<?php

declare(strict_types=1);

if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../ai/LocalAIApi.php';

function library_get_language(): string
{
    // Check GET
    if (isset($_GET['lang']) && in_array($_GET['lang'], ['en', 'ar'])) {
        $_SESSION['lang'] = $_GET['lang'];
    }
    
    // Check Session or Default
    return $_SESSION['lang'] ?? 'en';
}

function library_is_arabic_text(?string $text): bool
{
    if ($text === null) {
        return false;
    }

    return preg_match('/\p{Arabic}/u', $text) === 1;
}

function library_text_lang(?string $text, ?string $fallback = null): string
{
    if (library_is_arabic_text($text)) {
        return 'ar';
    }

    return $fallback ?? 'en';
}

function library_text_dir(?string $text, ?string $fallback = null): string
{
    return library_text_lang($text, $fallback) === 'ar' ? 'rtl' : 'ltr';
}

function library_trans(string $key, ?string $lang = null): string
{
    $lang = $lang ?? library_get_language();
    
    $translations = [
        'en' => [
            'catalog' => 'Catalog',
            'admin_studio' => 'Admin Studio',
            'nabd_library' => 'Nabd Library',
            'tagline' => 'Arabic · English e-library',
            'search' => 'Search...', 
            'switch_lang' => 'العربية',
            'switch_lang_code' => 'ar',
            'browse_public' => 'Browse public catalog',
            'open_admin' => 'Open Admin Studio',
            'upload_docs' => 'Upload documents, publish public/private titles, read online, and request AI summaries.',
            'mvp_label' => 'Bilingual reader MVP',
            'admin_panel' => 'Admin Panel',
            'dashboard' => 'Dashboard',
            'material_entry' => 'Material Entry',
            'categories' => 'Categories',
            'subcategories' => 'Subcategories',
            'types' => 'Types',
            'return_to_site' => 'Return to Site',
            'total_documents' => 'Total Documents',
            'public_titles' => 'Public Titles',
            'private_titles' => 'Private Titles',
            'total_downloads' => 'Total Downloads',
            'recent_documents' => 'Recent Documents',
            'title_author' => 'Title / Author',
            'type_category' => 'Type / Category',
            'visibility' => 'Visibility',
            'actions' => 'Actions',
            'view' => 'View',
            'showing_last_10' => 'Showing last 10 uploads',
            'manage_documents' => 'Manage Documents',
            'manage_documents_desc' => 'Manage your library documents, upload new materials, and edit metadata including bilingual titles, summaries, and more.',
            'create_new' => 'Create New',
            'edit' => 'Edit',
            'delete' => 'Delete',
            'save' => 'Save',
            'cancel' => 'Cancel',
            'name_en' => 'English Name',
            'name_ar' => 'Arabic Name',
            'description_en' => 'English Description',
            'description_ar' => 'Arabic Description',
            'confirm_delete' => 'Are you sure you want to delete this item?',
            'manage_categories_desc' => 'Manage document categories.',
            'add_new_category' => 'Add New Category',
            'search_placeholder' => 'Search...',
            'clear' => 'Clear',
            'name' => 'Name',
            'no_categories_found' => 'No categories found.',
            'add_new_document' => 'Add New Material',
            'search_docs_placeholder' => 'Search by title or author...',
            'id' => 'ID',
            'cover' => 'Cover',
            'year' => 'Year',
            'publisher' => 'Publisher',
            'country' => 'Country',
            'page_count' => 'Total Pages',
            'summary_en' => 'Summary (English)',
            'summary_ar' => 'Summary (Arabic)',
            'cover_image' => 'Front Cover Image',
            'document_file' => 'Document File',
            'visibility_permissions' => 'Visibility & Permissions',
            'download' => 'Download',
            'print' => 'Print',
            'copy' => 'Copy',
            'author' => 'Author',
            'select_type' => 'Select Type...',
            'select_category' => 'Select Category...',
            'select_subcategory' => 'Select Subcategory...',
            'required_new_docs' => 'Required for new documents.',
            'leave_empty_keep' => 'Leave empty to keep existing file.',
            'save_changes' => 'Save Changes',
            'manage_subcategories_desc' => 'Manage document subcategories.',
            'add_new_subcategory' => 'Add New Subcategory',
            'no_subcategories_found' => 'No subcategories found.',
            'category' => 'Category',
            'manage_types_desc' => 'Manage document types.',
            'add_new_type' => 'Add New Type',
            'no_types_found' => 'No types found.',
            'previous' => 'Previous',
            'next' => 'Next',
            'showing_page' => 'Showing page',
            'of' => 'of',
            'no_documents_found' => 'No documents found.',
            'submission_error' => 'Submission Error',
            'current_cover' => 'Current Cover:',
            'visibility_public' => 'Public',
            'visibility_private' => 'Private',
            'unknown' => 'Unknown',
            'translation_to_arabic' => 'Translate to Arabic',
            'translation_to_english' => 'Translate to English',
            'translation_enter_text' => 'Please enter text to translate.',
            'translating' => 'Translating...',
            'translation_failed' => 'Translation failed. Please try again.',
            'translation_error_prefix' => 'Translation error:',
            'category_name_required' => 'Both English and Arabic names are required for Category.',
            'category_update_required' => 'ID, English name, and Arabic name are required.',
            'invalid_category_id' => 'Invalid Category ID.',
            'category_created_success' => 'Category created successfully.',
            'category_updated_success' => 'Category updated successfully.',
            'category_deleted_success' => 'Category deleted successfully.',
            'subcategory_name_required' => 'Category, English name, and Arabic name are required.',
            'subcategory_update_required' => 'ID, Category, English name, and Arabic name are required.',
            'invalid_subcategory_id' => 'Invalid Subcategory ID.',
            'subcategory_created_success' => 'Subcategory created successfully.',
            'subcategory_updated_success' => 'Subcategory updated successfully.',
            'subcategory_deleted_success' => 'Subcategory deleted successfully.',
            'type_name_required' => 'Both English and Arabic names are required for Type.',
            'type_update_required' => 'ID, English name, and Arabic name are required.',
            'invalid_type_id' => 'Invalid Type ID.',
            'type_created_success' => 'Type created successfully.',
            'type_updated_success' => 'Type updated successfully.',
            'type_deleted_success' => 'Type deleted successfully.',
            'invalid_document_id' => 'Invalid Document ID.',
            'document_created_success' => 'Document created successfully.',
            'document_updated_success' => 'Document updated successfully.',
            'document_deleted_success' => 'Document deleted successfully.'
        ],
        'ar' => [
            'catalog' => 'الفهرس',
            'admin_studio' => 'استوديو الإدارة',
            'nabd_library' => 'مكتبة نبض',
            'tagline' => 'مكتبة إلكترونية عربية · إنجليزية',
            'search' => 'بحث...',
            'switch_lang' => 'English',
            'switch_lang_code' => 'en',
            'browse_public' => 'تصفح الفهرس العام',
            'open_admin' => 'فتح استوديو الإدارة',
            'upload_docs' => 'ارفع المستندات، انشر العناوين العامة/الخاصة، اقرأ عبر الإنترنت، واطلب ملخصات الذكاء الاصطناعي.',
            'mvp_label' => 'نموذج القارئ ثنائي اللغة',
            'admin_panel' => 'لوحة التحكم',
            'dashboard' => 'لوحة القيادة',
            'material_entry' => 'إدخال المواد',
            'categories' => 'التصنيف',
            'subcategories' => 'التصنيف الفرعي',
            'types' => 'الأنواع',
            'return_to_site' => 'العودة للموقع',
            'total_documents' => 'إجمالي المستندات',
            'public_titles' => 'العناوين العامة',
            'private_titles' => 'العناوين الخاصة',
            'total_downloads' => 'إجمالي التنزيلات',
            'recent_documents' => 'أحدث المستندات',
            'title_author' => 'العنوان / المؤلف',
            'type_category' => 'النوع / الفئة',
            'visibility' => 'الظهور',
            'actions' => 'الإجراءات',
            'view' => 'عرض',
            'showing_last_10' => 'عرض آخر 10 ملفات',
            'manage_documents' => 'إدارة المستندات',
            'manage_documents_desc' => 'إدارة مستندات المكتبة، رفع مواد جديدة، وتعديل البيانات الوصفية بما في ذلك العناوين والملخصات ثنائية اللغة.',
            'create_new' => 'إنشاء جديد',
            'edit' => 'تعديل',
            'delete' => 'حذف',
            'save' => 'حفظ',
            'cancel' => 'إلغاء',
            'name_en' => 'الاسم (إنجليزي)',
            'name_ar' => 'الاسم (عربي)',
            'description_en' => 'الوصف (إنجليزي)',
            'description_ar' => 'الوصف (عربي)',
            'confirm_delete' => 'هل أنت متأكد من حذف هذا العنصر؟',
            'manage_categories_desc' => 'إدارة التصنيف للمستندات',
            'add_new_category' => 'إضافة فئة جديدة',
            'search_placeholder' => 'بحث...',
            'clear' => 'مسح',
            'name' => 'الاسم',
            'no_categories_found' => 'لم يتم العثور على فئات.',
            'add_new_document' => 'إضافة مادة جديدة',
            'search_docs_placeholder' => 'بحث بالعنوان أو المؤلف...',
            'id' => 'المعرف',
            'cover' => 'الغلاف',
            'year' => 'السنة',
            'publisher' => 'الناشر',
            'country' => 'الدولة',
            'page_count' => 'عدد الصفحات',
            'summary_en' => 'الملخص (إنجليزي)',
            'summary_ar' => 'الملخص (عربي)',
            'cover_image' => 'صورة الغلاف الأمامي',
            'document_file' => 'ملف المستند',
            'visibility_permissions' => 'الظهور والصلاحيات',
            'download' => 'تنزيل',
            'print' => 'طباعة',
            'copy' => 'نسخ',
            'author' => 'المؤلف',
            'select_type' => 'اختر النوع...',
            'select_category' => 'اختر الفئة...',
            'select_subcategory' => 'اختر الفئة الفرعية...',
            'required_new_docs' => 'مطلوب للمستندات الجديدة.',
            'leave_empty_keep' => 'اتركه فارغاً للاحتفاظ بالملف الحالي.',
            'save_changes' => 'حفظ التغييرات',
            'manage_subcategories_desc' => 'إدارة التصنيف الفرعي للمستندات.',
            'add_new_subcategory' => 'إضافة فئة فرعية جديدة',
            'no_subcategories_found' => 'لم يتم العثور على فئات فرعية.',
            'category' => 'الفئة',
            'manage_types_desc' => 'إدارة أنواع المستندات.',
            'add_new_type' => 'إضافة نوع جديد',
            'no_types_found' => 'لم يتم العثور على أنواع.',
            'previous' => 'السابق',
            'next' => 'التالي',
            'showing_page' => 'عرض صفحة',
            'of' => 'من',
            'no_documents_found' => 'لم يتم العثور على مستندات.',
            'submission_error' => 'خطأ في الإرسال',
            'current_cover' => 'الغلاف الحالي:',
            'visibility_public' => 'عام',
            'visibility_private' => 'خاص',
            'unknown' => 'غير معروف',
            'translation_to_arabic' => 'ترجمة إلى العربية',
            'translation_to_english' => 'ترجمة إلى الإنجليزية',
            'translation_enter_text' => 'يرجى إدخال نص للترجمة.',
            'translating' => 'جارٍ الترجمة...',
            'translation_failed' => 'فشلت الترجمة. يرجى المحاولة مرة أخرى.',
            'translation_error_prefix' => 'خطأ في الترجمة:',
            'category_name_required' => 'اسما التصنيف بالإنجليزية والعربية مطلوبان.',
            'category_update_required' => 'المعرف واسم التصنيف بالإنجليزية والعربية مطلوبة.',
            'invalid_category_id' => 'معرف التصنيف غير صالح.',
            'category_created_success' => 'تم إنشاء التصنيف بنجاح.',
            'category_updated_success' => 'تم تحديث التصنيف بنجاح.',
            'category_deleted_success' => 'تم حذف التصنيف بنجاح.',
            'subcategory_name_required' => 'التصنيف واسم التصنيف الفرعي بالإنجليزية والعربية مطلوبة.',
            'subcategory_update_required' => 'المعرف والتصنيف واسم التصنيف الفرعي بالإنجليزية والعربية مطلوبة.',
            'invalid_subcategory_id' => 'معرف التصنيف الفرعي غير صالح.',
            'subcategory_created_success' => 'تم إنشاء التصنيف الفرعي بنجاح.',
            'subcategory_updated_success' => 'تم تحديث التصنيف الفرعي بنجاح.',
            'subcategory_deleted_success' => 'تم حذف التصنيف الفرعي بنجاح.',
            'type_name_required' => 'اسما النوع بالإنجليزية والعربية مطلوبان.',
            'type_update_required' => 'المعرف واسم النوع بالإنجليزية والعربية مطلوبة.',
            'invalid_type_id' => 'معرف النوع غير صالح.',
            'type_created_success' => 'تم إنشاء النوع بنجاح.',
            'type_updated_success' => 'تم تحديث النوع بنجاح.',
            'type_deleted_success' => 'تم حذف النوع بنجاح.',
            'invalid_document_id' => 'معرف المستند غير صالح.',
            'document_created_success' => 'تم إنشاء المستند بنجاح.',
            'document_updated_success' => 'تم تحديث المستند بنجاح.',
            'document_deleted_success' => 'تم حذف المستند بنجاح.'
        ]
    ];

    return $translations[$lang][$key] ?? $key;
}

function library_bootstrap(): void
{
    static $booted = false;
    if ($booted) {
        return;
    }

    $migrationPath = __DIR__ . '/../db/migrations/001_library_documents.sql';
    if (is_file($migrationPath)) {
        $sql = file_get_contents($migrationPath);
        if (is_string($sql) && trim($sql) !== '') {
            db()->exec($sql);
        }
    }

    // Run new migrations if needed
    $migration2Path = __DIR__ . '/../db/migrations/002_add_library_metadata.sql';
    if (is_file($migration2Path)) {
        // Simple check if columns exist
        $exists = db()->query("SHOW COLUMNS FROM library_documents LIKE 'category_ar'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration2Path);
            db()->exec($sql);
        }
    }

    $migration3Path = __DIR__ . '/../db/migrations/003_normalize_categories.sql';
    if (is_file($migration3Path)) {
        // Simple check if table exists
        $exists = db()->query("SHOW TABLES LIKE 'library_categories'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration3Path);
            db()->exec($sql);
        }
    }

    $migration4Path = __DIR__ . '/../db/migrations/004_create_types_table.sql';
    if (is_file($migration4Path)) {
        // Simple check if table exists
        $exists = db()->query("SHOW TABLES LIKE 'library_types'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration4Path);
            db()->exec($sql);
        }
    }

    $migration5Path = __DIR__ . '/../db/migrations/005_update_document_fields.sql';
    if (is_file($migration5Path)) {
        // Check if column exists
        $exists = db()->query("SHOW COLUMNS FROM library_documents LIKE 'cover_image_path'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration5Path);
            db()->exec($sql);
        }
    }

    $migration6Path = __DIR__ . '/../db/migrations/006_add_author_column.sql';
    if (is_file($migration6Path)) {
        // Check if column exists
        $exists = db()->query("SHOW COLUMNS FROM library_documents LIKE 'author'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration6Path);
            db()->exec($sql);
        }
    }

    $migration7Path = __DIR__ . '/../db/migrations/007_create_reader_activity.sql';
    if (is_file($migration7Path)) {
        $exists = db()->query("SHOW TABLES LIKE 'library_readers'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration7Path);
            if (is_string($sql) && trim($sql) !== '') {
                db()->exec($sql);
            }
        }
    }

    $migration8Path = __DIR__ . '/../db/migrations/008_create_library_settings.sql';
    if (is_file($migration8Path)) {
        $exists = db()->query("SHOW TABLES LIKE 'library_settings'")->fetch();
        if (!$exists) {
            $sql = file_get_contents($migration8Path);
            if (is_string($sql) && trim($sql) !== '') {
                db()->exec($sql);
            }
        }
    }

    $uploadDir = __DIR__ . '/../uploads/library';
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0775, true);
    }
    
    $coverDir = __DIR__ . '/../uploads/covers';
    if (!is_dir($coverDir)) {
        mkdir($coverDir, 0775, true);
    }

    $brandingDir = __DIR__ . '/../uploads/branding';
    if (!is_dir($brandingDir)) {
        mkdir($brandingDir, 0775, true);
    }

    library_seed_demo_documents();
    $booted = true;
}

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

function library_project_meta(): array
{
    $lang = library_get_language();

    return [
        'name' => library_profile_name($lang),
        'description' => library_profile_description($lang),
        'image' => $_SERVER['PROJECT_IMAGE_URL'] ?? '',
    ];
}

function library_profile_defaults(): array
{
    return [
        'library_name_en' => $_SERVER['PROJECT_NAME'] ?? 'Nabd Library',
        'library_name_ar' => 'مكتبة نبض',
        'short_name' => 'NL',
        'tagline_en' => 'Arabic · English e-library',
        'tagline_ar' => 'مكتبة رقمية عربية · إنجليزية',
        'description_en' => $_SERVER['PROJECT_DESCRIPTION'] ?? 'Bilingual electronic library for Arabic and English documents, online reading, and AI-assisted summaries.',
        'description_ar' => 'مكتبة إلكترونية ثنائية اللغة للوثائق العربية والإنجليزية، مع قراءة عبر الإنترنت وملخصات مدعومة بالذكاء الاصطناعي.',
        'contact_email' => '',
        'contact_phone' => '',
        'whatsapp_number' => '',
        'website_url' => '',
        'address_en' => '',
        'address_ar' => '',
        'opening_hours_en' => '',
        'opening_hours_ar' => '',
        'facebook_url' => '',
        'instagram_url' => '',
        'x_url' => '',
        'youtube_url' => '',
        'logo_path' => '',
        'favicon_path' => '',
        'copyright_text_en' => '',
        'copyright_text_ar' => '',
    ];
}

function library_get_profile(): array
{
    static $profile = null;
    if ($profile !== null) {
        return $profile;
    }

    library_bootstrap();
    $defaults = library_profile_defaults();
    $stmt = db()->query('SELECT * FROM library_settings ORDER BY id ASC LIMIT 1');
    $row = $stmt ? ($stmt->fetch() ?: []) : [];
    $profile = array_merge($defaults, is_array($row) ? $row : []);

    return $profile;
}

function library_profile_name(?string $lang = null): string
{
    $lang = $lang ?? library_get_language();
    $profile = library_get_profile();

    return library_localized_value(
        $profile['library_name_en'] ?? null,
        $profile['library_name_ar'] ?? null,
        $lang,
        $_SERVER['PROJECT_NAME'] ?? 'Nabd Library'
    );
}

function library_profile_tagline(?string $lang = null): string
{
    $lang = $lang ?? library_get_language();
    $profile = library_get_profile();

    return library_localized_value(
        $profile['tagline_en'] ?? null,
        $profile['tagline_ar'] ?? null,
        $lang,
        ''
    );
}

function library_profile_description(?string $lang = null): string
{
    $lang = $lang ?? library_get_language();
    $profile = library_get_profile();

    return library_localized_value(
        $profile['description_en'] ?? null,
        $profile['description_ar'] ?? null,
        $lang,
        $_SERVER['PROJECT_DESCRIPTION'] ?? 'Bilingual electronic library for Arabic and English documents, online reading, and AI-assisted summaries.'
    );
}

function library_profile_address(?string $lang = null): string
{
    $lang = $lang ?? library_get_language();
    $profile = library_get_profile();

    return library_localized_value(
        $profile['address_en'] ?? null,
        $profile['address_ar'] ?? null,
        $lang,
        ''
    );
}

function library_profile_opening_hours(?string $lang = null): string
{
    $lang = $lang ?? library_get_language();
    $profile = library_get_profile();

    return library_localized_value(
        $profile['opening_hours_en'] ?? null,
        $profile['opening_hours_ar'] ?? null,
        $lang,
        ''
    );
}

function library_normalize_profile_value($value, int $maxLength = 0): ?string
{
    $normalized = trim((string) $value);
    if ($normalized === '') {
        return null;
    }

    if ($maxLength > 0 && function_exists('mb_substr')) {
        $normalized = mb_substr($normalized, 0, $maxLength);
    } elseif ($maxLength > 0) {
        $normalized = substr($normalized, 0, $maxLength);
    }

    return $normalized;
}

function library_normalize_profile_url($value): ?string
{
    $normalized = trim((string) $value);
    if ($normalized === '') {
        return null;
    }

    if (!preg_match('~^https?://~i', $normalized)) {
        $normalized = 'https://' . ltrim($normalized, '/');
    }

    if (!filter_var($normalized, FILTER_VALIDATE_URL)) {
        throw new RuntimeException('Please provide valid URLs for website and social links.');
    }

    return $normalized;
}

function library_handle_brand_asset(array $file, string $kind): ?string
{
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        return null;
    }

    $originalName = (string) ($file['name'] ?? '');
    $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
    $rules = [
        'logo' => [
            'allowed' => ['jpg', 'jpeg', 'png', 'webp'],
            'max_size' => 4 * 1024 * 1024,
            'label' => 'logo',
        ],
        'favicon' => [
            'allowed' => ['ico', 'png', 'webp'],
            'max_size' => 1024 * 1024,
            'label' => 'favicon',
        ],
    ];

    if (!isset($rules[$kind])) {
        throw new RuntimeException('Unsupported upload type.');
    }

    $rule = $rules[$kind];
    if (!in_array($extension, $rule['allowed'], true)) {
        throw new RuntimeException(sprintf('Unsupported %s format. Allowed: %s.', $rule['label'], implode(', ', $rule['allowed'])));
    }

    $size = (int) ($file['size'] ?? 0);
    if ($size <= 0 || $size > $rule['max_size']) {
        throw new RuntimeException(sprintf('The %s file is too large.', $rule['label']));
    }

    $safeBase = preg_replace('/[^a-zA-Z0-9_-]+/', '-', pathinfo($originalName, PATHINFO_FILENAME)) ?: $kind;
    $storedName = strtolower($kind . '-' . date('YmdHis') . '-' . $safeBase . '-' . bin2hex(random_bytes(4)) . '.' . $extension);
    $relativePath = 'uploads/branding/' . $storedName;
    $absolutePath = __DIR__ . '/../' . $relativePath;

    if (!move_uploaded_file((string) $file['tmp_name'], $absolutePath)) {
        throw new RuntimeException(sprintf('Unable to save the %s file.', $rule['label']));
    }

    return $relativePath;
}

function library_save_profile(array $payload, array $files = []): void
{
    library_bootstrap();

    $profile = library_get_profile();

    $libraryNameEn = library_normalize_profile_value($payload['library_name_en'] ?? null, 255);
    $libraryNameAr = library_normalize_profile_value($payload['library_name_ar'] ?? null, 255);
    if ($libraryNameEn === null && $libraryNameAr === null) {
        throw new RuntimeException('Please provide at least one library name.');
    }

    $contactEmail = library_normalize_profile_value($payload['contact_email'] ?? null, 255);
    if ($contactEmail !== null && !filter_var($contactEmail, FILTER_VALIDATE_EMAIL)) {
        throw new RuntimeException('Please provide a valid contact email address.');
    }

    $logoPath = $profile['logo_path'] ?? null;
    $faviconPath = $profile['favicon_path'] ?? null;
    $newLogo = library_handle_brand_asset($files['logo_file'] ?? [], 'logo');
    $newFavicon = library_handle_brand_asset($files['favicon_file'] ?? [], 'favicon');
    if ($newLogo !== null) {
        $logoPath = $newLogo;
    }
    if ($newFavicon !== null) {
        $faviconPath = $newFavicon;
    }

    $data = [
        'library_name_en' => $libraryNameEn,
        'library_name_ar' => library_normalize_profile_value($payload['library_name_ar'] ?? null, 255),
        'short_name' => library_normalize_profile_value($payload['short_name'] ?? null, 32),
        'tagline_en' => library_normalize_profile_value($payload['tagline_en'] ?? null, 255),
        'tagline_ar' => library_normalize_profile_value($payload['tagline_ar'] ?? null, 255),
        'description_en' => library_normalize_profile_value($payload['description_en'] ?? null),
        'description_ar' => library_normalize_profile_value($payload['description_ar'] ?? null),
        'contact_email' => $contactEmail,
        'contact_phone' => library_normalize_profile_value($payload['contact_phone'] ?? null, 80),
        'whatsapp_number' => library_normalize_profile_value($payload['whatsapp_number'] ?? null, 80),
        'website_url' => library_normalize_profile_url($payload['website_url'] ?? null),
        'address_en' => library_normalize_profile_value($payload['address_en'] ?? null, 255),
        'address_ar' => library_normalize_profile_value($payload['address_ar'] ?? null, 255),
        'opening_hours_en' => library_normalize_profile_value($payload['opening_hours_en'] ?? null, 255),
        'opening_hours_ar' => library_normalize_profile_value($payload['opening_hours_ar'] ?? null, 255),
        'facebook_url' => library_normalize_profile_url($payload['facebook_url'] ?? null),
        'instagram_url' => library_normalize_profile_url($payload['instagram_url'] ?? null),
        'x_url' => library_normalize_profile_url($payload['x_url'] ?? null),
        'youtube_url' => library_normalize_profile_url($payload['youtube_url'] ?? null),
        'logo_path' => $logoPath,
        'favicon_path' => $faviconPath,
        'copyright_text_en' => library_normalize_profile_value($payload['copyright_text_en'] ?? null, 255),
        'copyright_text_ar' => library_normalize_profile_value($payload['copyright_text_ar'] ?? null, 255),
    ];

    $existingId = db()->query('SELECT id FROM library_settings ORDER BY id ASC LIMIT 1')->fetchColumn();
    if ($existingId) {
        $sql = 'UPDATE library_settings SET
            library_name_en = :library_name_en,
            library_name_ar = :library_name_ar,
            short_name = :short_name,
            tagline_en = :tagline_en,
            tagline_ar = :tagline_ar,
            description_en = :description_en,
            description_ar = :description_ar,
            contact_email = :contact_email,
            contact_phone = :contact_phone,
            whatsapp_number = :whatsapp_number,
            website_url = :website_url,
            address_en = :address_en,
            address_ar = :address_ar,
            opening_hours_en = :opening_hours_en,
            opening_hours_ar = :opening_hours_ar,
            facebook_url = :facebook_url,
            instagram_url = :instagram_url,
            x_url = :x_url,
            youtube_url = :youtube_url,
            logo_path = :logo_path,
            favicon_path = :favicon_path,
            copyright_text_en = :copyright_text_en,
            copyright_text_ar = :copyright_text_ar
            WHERE id = :id';
        $stmt = db()->prepare($sql);
        $data['id'] = (int) $existingId;
        $stmt->execute($data);
    } else {
        $sql = 'INSERT INTO library_settings (
            library_name_en, library_name_ar, short_name, tagline_en, tagline_ar,
            description_en, description_ar, contact_email, contact_phone, whatsapp_number,
            website_url, address_en, address_ar, opening_hours_en, opening_hours_ar,
            facebook_url, instagram_url, x_url, youtube_url, logo_path, favicon_path,
            copyright_text_en, copyright_text_ar
        ) VALUES (
            :library_name_en, :library_name_ar, :short_name, :tagline_en, :tagline_ar,
            :description_en, :description_ar, :contact_email, :contact_phone, :whatsapp_number,
            :website_url, :address_en, :address_ar, :opening_hours_en, :opening_hours_ar,
            :facebook_url, :instagram_url, :x_url, :youtube_url, :logo_path, :favicon_path,
            :copyright_text_en, :copyright_text_ar
        )';
        $stmt = db()->prepare($sql);
        $stmt->execute($data);
    }

    unset($GLOBALS['library_profile_cache']);
}

function library_set_flash(string $type, string $message): void
{
    $_SESSION['library_flash'][] = ['type' => $type, 'message' => $message];
}

function library_get_flashes(): array
{
    $flashes = $_SESSION['library_flash'] ?? [];
    unset($_SESSION['library_flash']);
    return is_array($flashes) ? $flashes : [];
}

function library_seed_demo_documents(): void
{
    $count = (int) (db()->query('SELECT COUNT(*) FROM library_documents')->fetchColumn() ?: 0);
    if ($count > 0) {
        return;
    }

    $pdfRelative = 'uploads/library/demo-library-guide.pdf';
    $txtRelative = 'uploads/library/demo-bilingual-notes.txt';
    $pdfAbsolute = __DIR__ . '/../' . $pdfRelative;
    $txtAbsolute = __DIR__ . '/../' . $txtRelative;
}

function library_old(string $key, string $default = ''): string
{
    return isset($_POST[$key]) ? trim((string) $_POST[$key]) : $default;
}

function library_localized_value(?string $english, ?string $arabic, ?string $lang = null, string $fallback = ''): string
{
    $lang = $lang ?? library_get_language();
    $primary = trim((string) ($lang === 'ar' ? $arabic : $english));
    $secondary = trim((string) ($lang === 'ar' ? $english : $arabic));

    if ($primary !== '') {
        return $primary;
    }

    if ($secondary !== '') {
        return $secondary;
    }

    return $fallback;
}

function library_localized_document_title(array $document, ?string $lang = null, string $fallback = 'Untitled'): string
{
    return library_localized_value(
        $document['title_en'] ?? null,
        $document['title_ar'] ?? null,
        $lang,
        $fallback
    );
}

function library_localized_document_description(array $document, ?string $lang = null, string $fallback = ''): string
{
    return library_localized_value(
        $document['description_en'] ?? null,
        $document['description_ar'] ?? null,
        $lang,
        $fallback
    );
}

function library_localized_document_summary(array $document, ?string $lang = null, string $fallback = ''): string
{
    $summary = library_localized_value(
        $document['summary_en'] ?? null,
        $document['summary_ar'] ?? null,
        $lang,
        ''
    );

    if ($summary !== '') {
        return $summary;
    }

    $combined = trim((string) ($document['summary_text'] ?? ''));
    return $combined !== '' ? $combined : $fallback;
}

function library_document_type_label(string $type): string
{
    $lang = library_get_language();
    $map = [
        'en' => [
            'pdf' => 'PDF reader',
            'txt' => 'Text note',
            'doc' => 'Word document',
            'docx' => 'Word document',
            'ppt' => 'PowerPoint',
            'pptx' => 'PowerPoint',
        ],
        'ar' => [
            'pdf' => 'قارئ PDF',
            'txt' => 'ملاحظة نصية',
            'doc' => 'مستند Word',
            'docx' => 'مستند Word',
            'ppt' => 'عرض PowerPoint',
            'pptx' => 'عرض PowerPoint',
        ],
    ];

    return $map[$lang][strtolower($type)] ?? strtoupper($type);
}

function library_language_label(string $lang): string
{
    $uiLang = library_get_language();
    $map = [
        'en' => [
            'en' => 'English',
            'ar' => 'Arabic',
            'bilingual' => 'Bilingual',
        ],
        'ar' => [
            'en' => 'الإنجليزية',
            'ar' => 'العربية',
            'bilingual' => 'ثنائي اللغة',
        ],
    ];

    return $map[$uiLang][$lang] ?? ($uiLang === 'ar' ? 'غير معروف' : 'Unknown');
}

function library_visibility_label(string $visibility): string
{
    if (library_get_language() === 'ar') {
        return $visibility === 'private' ? 'خاص / يتطلب تسجيل الدخول' : 'عام';
    }

    return $visibility === 'private' ? 'Private / login' : 'Public';
}

function library_allowed_extensions(): array
{
    return [
        'pdf' => 'PDF reader',
        'txt' => 'Text note',
        'doc' => 'Word document',
        'docx' => 'Word document',
        'ppt' => 'PowerPoint',
        'pptx' => 'PowerPoint',
    ];
}

function library_file_url(string $path): string
{
    if ($path === '') return '';
    if ($path[0] !== '/') {
        return '/' . $path;
    }
    return $path;
}

function library_can_preview(array $doc): bool
{
    // Only PDFs are supported for inline preview in this version
    return strtolower((string) ($doc['document_type'] ?? '')) === 'pdf';
}

function library_increment_views(int $id): void
{
    library_bootstrap();
    $stmt = db()->prepare('UPDATE library_documents SET view_count = view_count + 1 WHERE id = ?');
    $stmt->execute([$id]);
}

function library_reader_cookie_name(): string
{
    return 'nabd_reader_token';
}

function library_current_request_path(): string
{
    $path = (string) parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH);
    $path = $path !== '' ? $path : '/';
    return substr($path, 0, 255);
}

function library_current_reader_token(bool $create = true): string
{
    static $token = null;
    if ($token !== null) {
        return $token;
    }

    $candidate = strtolower(trim((string) ($_SESSION['library_reader_token'] ?? ($_COOKIE[library_reader_cookie_name()] ?? ''))));
    if (preg_match('/^[a-f0-9]{64}$/', $candidate) === 1) {
        $_SESSION['library_reader_token'] = $candidate;
        $token = $candidate;
        return $candidate;
    }

    if (!$create) {
        $token = '';
        return '';
    }

    try {
        $candidate = bin2hex(random_bytes(32));
    } catch (Throwable $e) {
        $candidate = hash('sha256', uniqid('reader', true) . microtime(true));
    }

    $_SESSION['library_reader_token'] = $candidate;
    $_COOKIE[library_reader_cookie_name()] = $candidate;

    if (!headers_sent()) {
        setcookie(library_reader_cookie_name(), $candidate, [
            'expires' => time() + (60 * 60 * 24 * 365),
            'path' => '/',
            'httponly' => true,
            'samesite' => 'Lax',
            'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
        ]);
    }

    $token = $candidate;
    return $candidate;
}

function library_get_current_reader(bool $create = true): ?array
{
    static $reader = null;

    if (is_array($reader)) {
        return $reader;
    }

    library_bootstrap();
    $token = library_current_reader_token($create);
    if ($token === '') {
        return null;
    }

    $stmt = db()->prepare('SELECT * FROM library_readers WHERE reader_token = :token LIMIT 1');
    $stmt->bindValue(':token', $token, PDO::PARAM_STR);
    $stmt->execute();
    $reader = $stmt->fetch();

    if (!$reader && $create) {
        $insert = db()->prepare('INSERT INTO library_readers (reader_token, preferred_language, last_path, user_agent) VALUES (:token, :language, :path, :user_agent)');
        $insert->bindValue(':token', $token, PDO::PARAM_STR);
        $insert->bindValue(':language', library_get_language(), PDO::PARAM_STR);
        $insert->bindValue(':path', library_current_request_path(), PDO::PARAM_STR);
        $insert->bindValue(':user_agent', substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255), PDO::PARAM_STR);
        $insert->execute();

        $stmt->execute();
        $reader = $stmt->fetch();
    }

    return is_array($reader) ? $reader : null;
}

function library_touch_current_reader(int $readerId): void
{
    $stmt = db()->prepare('UPDATE library_readers SET preferred_language = :language, last_path = :path, user_agent = :user_agent, last_seen_at = NOW() WHERE id = :id');
    $stmt->bindValue(':language', library_get_language(), PDO::PARAM_STR);
    $stmt->bindValue(':path', library_current_request_path(), PDO::PARAM_STR);
    $stmt->bindValue(':user_agent', substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255), PDO::PARAM_STR);
    $stmt->bindValue(':id', $readerId, PDO::PARAM_INT);
    $stmt->execute();
}

function library_touch_current_visit(int $readerId): array
{
    static $visit = null;

    if (is_array($visit) && (int) ($visit['reader_id'] ?? 0) === $readerId) {
        return $visit;
    }

    $sessionKey = (string) ($_SESSION['library_visit_key'] ?? '');
    if (preg_match('/^[a-f0-9]{32}$/', $sessionKey) !== 1) {
        try {
            $sessionKey = bin2hex(random_bytes(16));
        } catch (Throwable $e) {
            $sessionKey = substr(hash('sha256', session_id() . microtime(true)), 0, 32);
        }
        $_SESSION['library_visit_key'] = $sessionKey;
    }

    $select = db()->prepare('SELECT * FROM library_reader_visits WHERE reader_id = :reader_id AND session_key = :session_key LIMIT 1');
    $select->bindValue(':reader_id', $readerId, PDO::PARAM_INT);
    $select->bindValue(':session_key', $sessionKey, PDO::PARAM_STR);
    $select->execute();
    $visit = $select->fetch();

    if ($visit) {
        $update = db()->prepare('UPDATE library_reader_visits SET last_activity_at = NOW(), page_views = page_views + 1 WHERE id = :id');
        $update->bindValue(':id', (int) $visit['id'], PDO::PARAM_INT);
        $update->execute();

        $select->execute();
        $visit = $select->fetch();
        return is_array($visit) ? $visit : [];
    }

    $insert = db()->prepare('INSERT INTO library_reader_visits (reader_id, session_key, entry_path) VALUES (:reader_id, :session_key, :entry_path)');
    $insert->bindValue(':reader_id', $readerId, PDO::PARAM_INT);
    $insert->bindValue(':session_key', $sessionKey, PDO::PARAM_STR);
    $insert->bindValue(':entry_path', library_current_request_path(), PDO::PARAM_STR);
    $insert->execute();

    $select->execute();
    $visit = $select->fetch();

    return is_array($visit) ? $visit : [];
}

function library_log_reader_activity(int $readerId, ?int $visitId, string $eventType, ?int $documentId = null, array $meta = [], string $context = 'public'): void
{
    $filteredMeta = array_filter($meta, static fn ($value) => $value !== null && $value !== '');
    $metaJson = $filteredMeta ? json_encode($filteredMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;

    $stmt = db()->prepare('INSERT INTO library_reader_activities (reader_id, visit_id, document_id, event_type, page_path, context, meta_json) VALUES (:reader_id, :visit_id, :document_id, :event_type, :page_path, :context, :meta_json)');
    $stmt->bindValue(':reader_id', $readerId, PDO::PARAM_INT);
    $stmt->bindValue(':visit_id', $visitId, $visitId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
    $stmt->bindValue(':document_id', $documentId, $documentId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
    $stmt->bindValue(':event_type', substr($eventType, 0, 50), PDO::PARAM_STR);
    $stmt->bindValue(':page_path', library_current_request_path(), PDO::PARAM_STR);
    $stmt->bindValue(':context', substr($context, 0, 20), PDO::PARAM_STR);
    $stmt->bindValue(':meta_json', $metaJson, $metaJson === null ? PDO::PARAM_NULL : PDO::PARAM_STR);
    $stmt->execute();
}

function library_track_request(string $eventType, ?int $documentId = null, array $meta = [], string $context = 'public'): void
{
    static $tracked = false;
    if ($tracked) {
        return;
    }

    $reader = library_get_current_reader(true);
    if (!$reader) {
        return;
    }

    $readerId = (int) ($reader['id'] ?? 0);
    if ($readerId <= 0) {
        return;
    }

    library_touch_current_reader($readerId);
    $visit = library_touch_current_visit($readerId);
    $visitId = isset($visit['id']) ? (int) $visit['id'] : null;

    library_log_reader_activity($readerId, $visitId, $eventType, $documentId, $meta, $context);
    $tracked = true;
}

function library_get_current_reader_summary(): ?array
{
    $reader = library_get_current_reader(true);
    if (!$reader) {
        return null;
    }

    $readerId = (int) ($reader['id'] ?? 0);
    if ($readerId <= 0) {
        return null;
    }

    $readerStmt = db()->prepare('SELECT * FROM library_readers WHERE id = :id LIMIT 1');
    $readerStmt->bindValue(':id', $readerId, PDO::PARAM_INT);
    $readerStmt->execute();
    $readerRow = $readerStmt->fetch();
    if (!$readerRow) {
        return null;
    }

    $stmt = db()->prepare(
        'SELECT
            (SELECT COUNT(*) FROM library_reader_visits WHERE reader_id = :reader_id_1) AS total_visits,
            (SELECT COUNT(*) FROM library_reader_activities WHERE reader_id = :reader_id_2) AS total_activities,
            (SELECT COUNT(DISTINCT document_id) FROM library_reader_activities WHERE reader_id = :reader_id_3 AND document_id IS NOT NULL) AS documents_opened,
            (SELECT MAX(created_at) FROM library_reader_activities WHERE reader_id = :reader_id_4 AND document_id IS NOT NULL) AS last_read_at'
    );
    $stmt->bindValue(':reader_id_1', $readerId, PDO::PARAM_INT);
    $stmt->bindValue(':reader_id_2', $readerId, PDO::PARAM_INT);
    $stmt->bindValue(':reader_id_3', $readerId, PDO::PARAM_INT);
    $stmt->bindValue(':reader_id_4', $readerId, PDO::PARAM_INT);
    $stmt->execute();
    $metrics = $stmt->fetch() ?: [];

    return array_merge($readerRow, $metrics);
}

function library_get_current_reader_history(int $limit = 8): array
{
    $reader = library_get_current_reader(true);
    if (!$reader) {
        return [];
    }

    $stmt = db()->prepare(
        'SELECT
            d.id,
            d.title_en,
            d.title_ar,
            d.author,
            d.document_language,
            d.cover_image_path,
            MAX(a.created_at) AS last_opened_at,
            COUNT(*) AS open_count,
            MAX(CASE WHEN a.event_type = "reader_opened" THEN a.created_at ELSE NULL END) AS last_reader_opened_at
        FROM library_reader_activities a
        INNER JOIN library_documents d ON d.id = a.document_id
        WHERE a.reader_id = :reader_id
          AND a.event_type IN ("document_viewed", "reader_opened")
        GROUP BY d.id, d.title_en, d.title_ar, d.author, d.document_language, d.cover_image_path
        ORDER BY last_opened_at DESC
        LIMIT :limit'
    );
    $stmt->bindValue(':reader_id', (int) $reader['id'], PDO::PARAM_INT);
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->execute();

    return $stmt->fetchAll() ?: [];
}

function library_get_current_reader_activities(int $limit = 16): array
{
    $reader = library_get_current_reader(true);
    if (!$reader) {
        return [];
    }

    $stmt = db()->prepare(
        'SELECT
            a.id,
            a.visit_id,
            a.document_id,
            a.event_type,
            a.page_path,
            a.context,
            a.meta_json,
            a.created_at,
            d.title_en,
            d.title_ar,
            d.author,
            d.document_language
        FROM library_reader_activities a
        LEFT JOIN library_documents d ON d.id = a.document_id
        WHERE a.reader_id = :reader_id
        ORDER BY a.created_at DESC
        LIMIT :limit'
    );
    $stmt->bindValue(':reader_id', (int) $reader['id'], PDO::PARAM_INT);
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->execute();

    return $stmt->fetchAll() ?: [];
}

function library_get_current_reader_visits(int $limit = 8): array
{
    $reader = library_get_current_reader(true);
    if (!$reader) {
        return [];
    }

    $stmt = db()->prepare(
        'SELECT
            v.id,
            v.entry_path,
            v.started_at,
            v.last_activity_at,
            v.page_views,
            (SELECT COUNT(*) FROM library_reader_activities a WHERE a.visit_id = v.id) AS activity_count,
            (SELECT COUNT(*) FROM library_reader_activities a WHERE a.visit_id = v.id AND a.document_id IS NOT NULL) AS document_count
        FROM library_reader_visits v
        WHERE v.reader_id = :reader_id
        ORDER BY v.started_at DESC
        LIMIT :limit'
    );
    $stmt->bindValue(':reader_id', (int) $reader['id'], PDO::PARAM_INT);
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->execute();

    return $stmt->fetchAll() ?: [];
}

function library_generate_summary(int $id): array
{
    library_bootstrap();
    $doc = library_fetch_document($id);
    if (!$doc) {
        return ['success' => false, 'message' => 'Document not found.'];
    }

    // 1. Try to extract text from the file
    $text = '';
    $filePath = __DIR__ . '/../' . ($doc['file_path'] ?? '');
    
    if (file_exists($filePath)) {
        $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        if ($ext === 'pdf') {
            // pdftotext is available via poppler-utils
            // -layout preserves structure
            // -f 1 -l 20 limits to first 20 pages (usually sufficient for summary)
            $cmd = sprintf("pdftotext -layout -f 1 -l 20 %s -", escapeshellarg($filePath));
            $text = shell_exec($cmd);
        } elseif (in_array($ext, ['txt', 'md', 'csv', 'json'])) {
            $text = file_get_contents($filePath);
        }
    }
    
    $text = trim((string)$text);

    // 2. Fallback to description/title if extraction failed or empty
    if ($text === '') {
        $descEn = trim((string) ($doc['description_en'] ?? ''));
        $descAr = trim((string) ($doc['description_ar'] ?? ''));
        $titleEn = trim((string) ($doc['title_en'] ?? ''));
        $titleAr = trim((string) ($doc['title_ar'] ?? ''));
        
        $text = "Title (EN): $titleEn\nTitle (AR): $titleAr\n\nDescription (EN): $descEn\nDescription (AR): $descAr";
    }

    // 3. Truncate text to avoid token limits (approx 20k chars)
    if (strlen($text) > 20000) {
        $text = substr($text, 0, 20000) . "\n... [truncated]";
    }

    // Use HEREDOC for cleaner string with quotes
    $prompt = <<<PROMPT
You are a bilingual summarization assistant.
Please summarize the following document content into:
1. A concise paragraph in English (summary_en).
2. A concise paragraph in Arabic (summary_ar).

Document Content:
"""
$text
"""

Return ONLY valid JSON with keys 'summary_en' and 'summary_ar'. Do not include markdown formatting.
PROMPT;

    // 4. Call AI (using 'input' key for LocalAIApi compatibility)
    $resp = LocalAIApi::createResponse([
        'model' => 'gpt-5-mini',
        'input' => [
            ['role' => 'system', 'content' => 'You are a helpful bilingual assistant that outputs JSON.'],
            ['role' => 'user', 'content' => $prompt],
        ]
    ]);

    if (empty($resp['success'])) {
        return ['success' => false, 'message' => 'AI request failed: ' . ($resp['error'] ?? 'Unknown error')];
    }

    $outputText = LocalAIApi::extractText($resp);
    
    // 5. Parse JSON
    $sumEn = '';
    $sumAr = '';

    // Attempt to parse JSON from response
    $jsonStart = strpos($outputText, '{');
    $jsonEnd = strrpos($outputText, '}');
    if ($jsonStart !== false && $jsonEnd !== false) {
        $jsonStr = substr($outputText, $jsonStart, $jsonEnd - $jsonStart + 1);
        $data = json_decode($jsonStr, true);
        if (is_array($data)) {
            $sumEn = $data['summary_en'] ?? '';
            $sumAr = $data['summary_ar'] ?? '';
        }
    }
    
    // Fallback if JSON parsing failed
    if (!$sumEn && !$sumAr) {
        $sumEn = $outputText; // Just dump the text
        $sumAr = '';
    }

    // 6. Update DB
    $stmt = db()->prepare('UPDATE library_documents SET summary_text = ?, summary_en = ?, summary_ar = ? WHERE id = ?');
    $combined = "English: $sumEn\n\nArabic: $sumAr";
    $stmt->execute([$combined, $sumEn, $sumAr, $id]);

    return ['success' => true, 'message' => 'Summary generated successfully from document content.'];
}

// --- Category Functions ---

function library_get_categories(string $search = ''): array
{
    // Backward compatibility
    $result = library_get_categories_paginated($search);
    return $result['data'];
}

function library_get_categories_paginated(string $search = '', int $limit = 0, int $offset = 0): array
{
    library_bootstrap();
    $sql = 'SELECT * FROM library_categories WHERE 1=1';
    $countSql = 'SELECT COUNT(*) FROM library_categories WHERE 1=1';
    
    $params = [];
    if ($search !== '') {
        $clause = ' AND (name_en LIKE ? OR name_ar LIKE ?)';
        $sql .= $clause;
        $countSql .= $clause;
        $params[] = "%$search%";
        $params[] = "%$search%";
    }
    
    $sql .= ' ORDER BY name_en ASC';
    
    if ($limit > 0) {
        $sql .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
    }
    
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    $data = $stmt->fetchAll() ?: [];
    
    $stmtCount = db()->prepare($countSql);
    $stmtCount->execute($params);
    $total = (int)$stmtCount->fetchColumn();

    return ['data' => $data, 'total' => $total];
}

function library_get_subcategories(?int $categoryId = null, string $search = ''): array
{
    // Backward compatibility
    $result = library_get_subcategories_paginated($categoryId, $search);
    return $result['data'];
}

function library_get_subcategories_paginated(?int $categoryId = null, string $search = '', int $limit = 0, int $offset = 0): array
{
    library_bootstrap();
    $sql = 'SELECT * FROM library_subcategories WHERE 1=1';
    $countSql = 'SELECT COUNT(*) FROM library_subcategories WHERE 1=1';
    $params = [];
    
    if ($categoryId !== null) {
        $clause = ' AND category_id = ?';
        $sql .= $clause;
        $countSql .= $clause;
        $params[] = $categoryId;
    }
    
    if ($search !== '') {
        $clause = ' AND (name_en LIKE ? OR name_ar LIKE ?)';
        $sql .= $clause;
        $countSql .= $clause;
        $params[] = "%$search%";
        $params[] = "%$search%";
    }
    
    $sql .= ' ORDER BY name_en ASC';
    
    if ($limit > 0) {
        $sql .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
    }
    
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    $data = $stmt->fetchAll() ?: [];
    
    $stmtCount = db()->prepare($countSql);
    $stmtCount->execute($params);
    $total = (int)$stmtCount->fetchColumn();

    return ['data' => $data, 'total' => $total];
}

function library_create_category(string $nameEn, string $nameAr): int
{
    library_bootstrap();
    $stmt = db()->prepare('INSERT INTO library_categories (name_en, name_ar) VALUES (?, ?)');
    $stmt->execute([$nameEn, $nameAr]);
    return (int) db()->lastInsertId();
}

function library_create_subcategory(int $categoryId, string $nameEn, string $nameAr): int
{
    library_bootstrap();
    $stmt = db()->prepare('INSERT INTO library_subcategories (category_id, name_en, name_ar) VALUES (?, ?, ?)');
    $stmt->execute([$categoryId, $nameEn, $nameAr]);
    return (int) db()->lastInsertId();
}

function library_get_category_by_id(int $id): ?array
{
    library_bootstrap();
    $stmt = db()->prepare('SELECT * FROM library_categories WHERE id = ?');
    $stmt->execute([$id]);
    return $stmt->fetch() ?: null;
}

function library_get_subcategory_by_id(int $id): ?array
{
    library_bootstrap();
    $stmt = db()->prepare('SELECT * FROM library_subcategories WHERE id = ?');
    $stmt->execute([$id]);
    return $stmt->fetch() ?: null;
}

function library_update_category(int $id, string $nameEn, string $nameAr): void
{
    library_bootstrap();
    $stmt = db()->prepare('UPDATE library_categories SET name_en = ?, name_ar = ? WHERE id = ?');
    $stmt->execute([$nameEn, $nameAr, $id]);
}

function library_delete_category(int $id): void
{
    library_bootstrap();
    $stmt = db()->prepare('DELETE FROM library_categories WHERE id = ?');
    $stmt->execute([$id]);
}

function library_update_subcategory(int $id, int $categoryId, string $nameEn, string $nameAr): void
{
    library_bootstrap();
    $stmt = db()->prepare('UPDATE library_subcategories SET category_id = ?, name_en = ?, name_ar = ? WHERE id = ?');
    $stmt->execute([$categoryId, $nameEn, $nameAr, $id]);
}

function library_delete_subcategory(int $id): void
{
    library_bootstrap();
    $stmt = db()->prepare('DELETE FROM library_subcategories WHERE id = ?');
    $stmt->execute([$id]);
}

// --- End Category Functions ---

// --- Type Functions ---

function library_get_types(string $search = ''): array
{
    $result = library_get_types_paginated($search);
    return $result['data'];
}

function library_get_types_paginated(string $search = '', int $limit = 0, int $offset = 0): array
{
    library_bootstrap();
    $sql = 'SELECT * FROM library_types WHERE 1=1';
    $countSql = 'SELECT COUNT(*) FROM library_types WHERE 1=1';
    $params = [];
    
    if ($search !== '') {
        $clause = ' AND (name_en LIKE ? OR name_ar LIKE ?)';
        $sql .= $clause;
        $countSql .= $clause;
        $params[] = "%$search%";
        $params[] = "%$search%";
    }
    
    $sql .= ' ORDER BY name_en ASC';
    
    if ($limit > 0) {
        $sql .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
    }

    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    $data = $stmt->fetchAll() ?: [];
    
    $stmtCount = db()->prepare($countSql);
    $stmtCount->execute($params);
    $total = (int)$stmtCount->fetchColumn();

    return ['data' => $data, 'total' => $total];
}

function library_create_type(string $nameEn, string $nameAr): int
{
    library_bootstrap();
    $stmt = db()->prepare('INSERT INTO library_types (name_en, name_ar) VALUES (?, ?)');
    $stmt->execute([$nameEn, $nameAr]);
    return (int) db()->lastInsertId();
}

function library_get_type_by_id(int $id): ?array
{
    library_bootstrap();
    $stmt = db()->prepare('SELECT * FROM library_types WHERE id = ?');
    $stmt->execute([$id]);
    return $stmt->fetch() ?: null;
}

function library_update_type(int $id, string $nameEn, string $nameAr): void
{
    library_bootstrap();
    $stmt = db()->prepare('UPDATE library_types SET name_en = ?, name_ar = ? WHERE id = ?');
    $stmt->execute([$nameEn, $nameAr, $id]);
}

function library_delete_type(int $id): void
{
    library_bootstrap();
    $stmt = db()->prepare('DELETE FROM library_types WHERE id = ?');
    $stmt->execute([$id]);
}

// --- End Type Functions ---

function library_fetch_documents(bool $publicOnly = false, array $filters = []): array
{
    // Backward compatibility
    $result = library_fetch_documents_paginated($publicOnly, $filters);
    return $result['data'];
}

function library_fetch_documents_paginated(bool $publicOnly = false, array $filters = [], int $limit = 0, int $offset = 0): array
{
    library_bootstrap();

    $sql = 'SELECT d.*, 
            c.name_en as cat_en, c.name_ar as cat_ar, 
            sc.name_en as sub_en, sc.name_ar as sub_ar,
            t.name_en as type_en, t.name_ar as type_ar
            FROM library_documents d 
            LEFT JOIN library_categories c ON d.category_id = c.id
            LEFT JOIN library_subcategories sc ON d.subcategory_id = sc.id
            LEFT JOIN library_types t ON d.type_id = t.id
            WHERE 1=1';
    
    $countSql = 'SELECT COUNT(*) FROM library_documents d 
            LEFT JOIN library_categories c ON d.category_id = c.id
            LEFT JOIN library_subcategories sc ON d.subcategory_id = sc.id
            LEFT JOIN library_types t ON d.type_id = t.id
            WHERE 1=1';
            
    $params = [];

    if ($publicOnly) {
        $clause = ' AND d.visibility = :visibility';
        $sql .= $clause;
        $countSql .= $clause;
        $params[':visibility'] = 'public';
    }

    // Apply Search Filters
    if (!empty($filters['q'])) {
        $q = trim($filters['q']);
        $clause = ' AND (d.title_en LIKE :q OR d.title_ar LIKE :q OR d.author LIKE :q OR d.description_en LIKE :q OR d.description_ar LIKE :q OR d.summary_en LIKE :q)';
        $sql .= $clause;
        $countSql .= $clause;
        $params[':q'] = "%$q%";
    }

    $sql .= ' ORDER BY d.is_featured DESC, d.created_at DESC';
    
    if ($limit > 0) {
        $sql .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
    }
    
    $stmt = db()->prepare($sql);
    foreach ($params as $key => $value) {
        $stmt->bindValue($key, $value);
    }
    $stmt->execute();
    $data = $stmt->fetchAll() ?: [];
    
    $stmtCount = db()->prepare($countSql);
    foreach ($params as $key => $value) {
        $stmtCount->bindValue($key, $value);
    }
    $stmtCount->execute();
    $total = (int)$stmtCount->fetchColumn();

    return ['data' => $data, 'total' => $total];
}

function library_fetch_document(int $id, bool $publicOnly = false): ?array
{
    library_bootstrap();

    $sql = 'SELECT d.*, 
            c.name_en as cat_en, c.name_ar as cat_ar, 
            sc.name_en as sub_en, sc.name_ar as sub_ar,
            t.name_en as type_en, t.name_ar as type_ar
            FROM library_documents d 
            LEFT JOIN library_categories c ON d.category_id = c.id
            LEFT JOIN library_subcategories sc ON d.subcategory_id = sc.id
            LEFT JOIN library_types t ON d.type_id = t.id
            WHERE d.id = :id';
    $params = [':id' => $id];

    if ($publicOnly) {
        $sql .= ' AND d.visibility = :visibility';
        $params[':visibility'] = 'public';
    }

    $stmt = db()->prepare($sql);
    foreach ($params as $key => $value) {
        $stmt->bindValue($key, $value);
    }
    $stmt->execute();

    return $stmt->fetch() ?: null;
}

function library_recent_documents(int $limit = 3, bool $publicOnly = false): array
{
    library_bootstrap();

    $sql = 'SELECT * FROM library_documents WHERE 1=1';
    if ($publicOnly) {
        $sql .= ' AND visibility = "public"';
    }
    $sql .= ' ORDER BY created_at DESC LIMIT ' . (int)$limit;

    $stmt = db()->query($sql);
    return $stmt ? $stmt->fetchAll() : [];
}

function library_catalog_metrics(): array
{
    library_bootstrap();

    $sql = 'SELECT
        COUNT(*) AS total_count,
        SUM(CASE WHEN visibility = "public" THEN 1 ELSE 0 END) AS public_count,
        SUM(CASE WHEN visibility = "private" THEN 1 ELSE 0 END) AS private_count,
        SUM(CASE WHEN summary_text IS NOT NULL THEN 1 ELSE 0 END) AS summarized_count
    FROM library_documents';

    $row = db()->query($sql)->fetch() ?: [];

    return [
        'total_count' => (int) ($row['total_count'] ?? 0),
        'public_count' => (int) ($row['public_count'] ?? 0),
        'private_count' => (int) ($row['private_count'] ?? 0),
        'summarized_count' => (int) ($row['summarized_count'] ?? 0),
    ];
}

function library_handle_uploaded_file(array $file): array
{
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Please upload a document file.');
    }

    $originalName = (string) ($file['name'] ?? '');
    $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
    $allowed = library_allowed_extensions();
    if (!isset($allowed[$extension])) {
        throw new RuntimeException('Unsupported file type.');
    }

    $size = (int) ($file['size'] ?? 0);
    if ($size <= 0 || $size > 12 * 1024 * 1024) {
        throw new RuntimeException('File must be smaller than 12 MB.');
    }

    $safeBase = preg_replace('/[^a-zA-Z0-9_-]+/', '-', pathinfo($originalName, PATHINFO_FILENAME)) ?: 'document';
    $storedName = strtolower(date('YmdHis') . '-' . $safeBase . '-' . bin2hex(random_bytes(4)) . '.' . $extension);
    $relativePath = 'uploads/library/' . $storedName;
    $absolutePath = __DIR__ . '/../' . $relativePath;

    if (!move_uploaded_file((string) $file['tmp_name'], $absolutePath)) {
        throw new RuntimeException('Unable to save the uploaded file.');
    }

    return [
        'file_name' => $originalName,
        'file_path' => $relativePath,
        'document_type' => $extension,
        'file_size_kb' => (int) ceil($size / 1024),
    ];
}

function library_handle_cover_image(array $file): ?string
{
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        return null;
    }

    $originalName = (string) ($file['name'] ?? '');
    $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
    $allowed = ['jpg', 'jpeg', 'png', 'webp', 'gif'];
    if (!in_array($extension, $allowed)) {
        throw new RuntimeException('Unsupported cover image type. Allowed: jpg, png, webp, gif.');
    }

    $size = (int) ($file['size'] ?? 0);
    if ($size <= 0 || $size > 5 * 1024 * 1024) {
        throw new RuntimeException('Cover image must be smaller than 5 MB.');
    }

    $safeBase = preg_replace('/[^a-zA-Z0-9_-]+/', '-', pathinfo($originalName, PATHINFO_FILENAME)) ?: 'cover';
    $storedName = strtolower(date('YmdHis') . '-' . $safeBase . '-' . bin2hex(random_bytes(4)) . '.' . $extension);
    $relativePath = 'uploads/covers/' . $storedName;
    $absolutePath = __DIR__ . '/../' . $relativePath;

    if (!move_uploaded_file((string) $file['tmp_name'], $absolutePath)) {
        throw new RuntimeException('Unable to save the cover image.');
    }

    return $relativePath;
}

function library_create_document(array $payload, array $file, array $coverFile = []): int
{
    library_bootstrap();

    $titleEn = trim((string) ($payload['title_en'] ?? ''));
    $titleAr = trim((string) ($payload['title_ar'] ?? ''));
    
    // Process IDs
    $categoryId = !empty($payload['category_id']) ? (int)$payload['category_id'] : null;
    $subcategoryId = !empty($payload['subcategory_id']) ? (int)$payload['subcategory_id'] : null;
    $typeId = !empty($payload['type_id']) ? (int)$payload['type_id'] : null;
    
    // Fetch names for backward compatibility if needed, or just store IDs
    $categoryName = '';
    $categoryNameAr = '';
    $subName = '';
    $subNameAr = '';

    if ($categoryId) {
        $cat = library_get_category_by_id($categoryId);
        if ($cat) {
            $categoryName = $cat['name_en'];
            $categoryNameAr = $cat['name_ar'];
        }
    }
    if ($subcategoryId) {
        $sub = library_get_subcategory_by_id($subcategoryId);
        if ($sub) {
            $subName = $sub['name_en'];
            $subNameAr = $sub['name_ar'];
        }
    }

    $visibility = (string) ($payload['visibility'] ?? 'public');
    $allow_download = !empty($payload['allow_download']) ? 1 : 0;
    $allow_print = !empty($payload['allow_print']) ? 1 : 0;
    $allow_copy = !empty($payload['allow_copy']) ? 1 : 0;

    // New Fields
    $publisher = trim((string) ($payload['publisher'] ?? ''));
    $publishYear = !empty($payload['publish_year']) ? (int)$payload['publish_year'] : null;
    $author = trim((string) ($payload['author'] ?? ''));
    $country = trim((string) ($payload['country'] ?? ''));
    $pageCount = !empty($payload['page_count']) ? (int)$payload['page_count'] : null;
    $summaryEn = trim((string) ($payload['summary_en'] ?? ''));
    $summaryAr = trim((string) ($payload['summary_ar'] ?? ''));
    $descriptionEn = trim((string) ($payload['description_en'] ?? ''));
    $descriptionAr = trim((string) ($payload['description_ar'] ?? ''));

    $fileData = library_handle_uploaded_file(file: $file);
    $coverPath = library_handle_cover_image($coverFile);

    $stmt = db()->prepare('INSERT INTO library_documents (
        title_en, title_ar, 
        category, category_ar, sub_category, sub_category_ar, 
        category_id, subcategory_id,
        visibility, document_type,
        file_name, file_path, file_size_kb, allow_download, allow_print, allow_copy,
        cover_image_path, publisher, publish_year, author, country, type_id, page_count, summary_en, summary_ar,
        description_en, description_ar
    ) VALUES (
        :title_en, :title_ar, 
        :category, :category__ar, :sub_category, :sub_category_ar, 
        :category_id, :subcategory_id,
        :visibility, :document_type,
        :file_name, :file_path, :file_size_kb, :allow_download, :allow_print, :allow_copy,
        :cover_image_path, :publisher, :publish_year, :author, :country, :type_id, :page_count, :summary_en, :summary_ar,
        :description_en, :description_ar
    )');

    $stmt->execute([
        ':title_en' => $titleEn ?: null,
        ':title_ar' => $titleAr ?: null,
        ':category' => $categoryName ?: null,
        ':category_ar' => $categoryNameAr ?: null,
        ':sub_category' => $subName ?: null,
        ':sub_category_ar' => $subNameAr ?: null,
        ':category_id' => $categoryId,
        ':subcategory_id' => $subcategoryId,
        ':visibility' => $visibility,
        ':document_type' => $fileData['document_type'],
        ':file_name' => $fileData['file_name'],
        ':file_path' => $fileData['file_path'],
        ':file_size_kb' => $fileData['file_size_kb'],
        ':allow_download' => $allow_download,
        ':allow_print' => $allow_print,
        ':allow_copy' => $allow_copy,
        ':cover_image_path' => $coverPath,
        ':publisher' => $publisher ?: null,
        ':publish_year' => $publishYear,
        ':author' => $author ?: null,
        ':country' => $country ?: null,
        ':type_id' => $typeId,
        ':page_count' => $pageCount,
        ':summary_en' => $summaryEn ?: null,
        ':summary_ar' => $summaryAr ?: null,
        ':description_en' => $descriptionEn ?: null,
        ':description_ar' => $descriptionAr ?: null,
    ]);

    return (int) db()->lastInsertId();
}

function library_update_document(int $id, array $payload, array $file = [], array $coverFile = []): void
{
    library_bootstrap();

    // Fetch existing document to keep file if not replaced
    $existing = library_fetch_document($id);
    if (!$existing) {
        throw new RuntimeException('Document not found.');
    }

    $titleEn = trim((string) ($payload['title_en'] ?? ''));
    $titleAr = trim((string) ($payload['title_ar'] ?? ''));
    
    // Process IDs
    $categoryId = !empty($payload['category_id']) ? (int)$payload['category_id'] : null;
    $subcategoryId = !empty($payload['subcategory_id']) ? (int)$payload['subcategory_id'] : null;
    $typeId = !empty($payload['type_id']) ? (int)$payload['type_id'] : null;
    
    $categoryName = '';
    $categoryNameAr = '';
    $subName = '';
    $subNameAr = '';

    if ($categoryId) {
        $cat = library_get_category_by_id($categoryId);
        if ($cat) {
            $categoryName = $cat['name_en'];
            $categoryNameAr = $cat['name_ar'];
        }
    }
    if ($subcategoryId) {
        $sub = library_get_subcategory_by_id($subcategoryId);
        if ($sub) {
            $subName = $sub['name_en'];
            $subNameAr = $sub['name_ar'];
        }
    }

    $visibility = (string) ($payload['visibility'] ?? 'public');
    $allow_download = !empty($payload['allow_download']) ? 1 : 0;
    $allow_print = !empty($payload['allow_print']) ? 1 : 0;
    $allow_copy = !empty($payload['allow_copy']) ? 1 : 0;

    $publisher = trim((string) ($payload['publisher'] ?? ''));
    $publishYear = !empty($payload['publish_year']) ? (int)$payload['publish_year'] : null;
    $author = trim((string) ($payload['author'] ?? ''));
    $country = trim((string) ($payload['country'] ?? ''));
    $pageCount = !empty($payload['page_count']) ? (int)$payload['page_count'] : null;
    $summaryEn = trim((string) ($payload['summary_en'] ?? ''));
    $summaryAr = trim((string) ($payload['summary_ar'] ?? ''));
    $descriptionEn = trim((string) ($payload['description_en'] ?? ''));
    $descriptionAr = trim((string) ($payload['description_ar'] ?? ''));

    // Handle File Update
    $fileData = null;
    if (!empty($file['name'])) {
        $fileData = library_handle_uploaded_file($file);
    }

    // Handle Cover Update
    $coverPath = null;
    if (!empty($coverFile['name'])) {
        $coverPath = library_handle_cover_image($coverFile);
    }

    $sql = 'UPDATE library_documents SET 
        title_en = :title_en, title_ar = :title_ar, 
        category = :category, category_ar = :category_ar, sub_category = :sub_category, sub_category_ar = :sub_category_ar, 
        category_id = :category_id, subcategory_id = :subcategory_id,
        visibility = :visibility,
        allow_download = :allow_download, allow_print = :allow_print, allow_copy = :allow_copy,
        publisher = :publisher, publish_year = :publish_year, author = :author, country = :country, 
        type_id = :type_id, page_count = :page_count, summary_en = :summary_en, summary_ar = :summary_ar,
        description_en = :description_en, description_ar = :description_ar';
    
    $params = [
        ':title_en' => $titleEn ?: null,
        ':title_ar' => $titleAr ?: null,
        ':category' => $categoryName ?: null,
        ':category_ar' => $categoryNameAr ?: null,
        ':sub_category' => $subName ?: null,
        ':sub_category_ar' => $subNameAr ?: null,
        ':category_id' => $categoryId,
        ':subcategory_id' => $subcategoryId,
        ':visibility' => $visibility,
        ':allow_download' => $allow_download,
        ':allow_print' => $allow_print,
        ':allow_copy' => $allow_copy,
        ':publisher' => $publisher ?: null,
        ':publish_year' => $publishYear,
        ':author' => $author ?: null,
        ':country' => $country ?: null,
        ':type_id' => $typeId,
        ':page_count' => $pageCount,
        ':summary_en' => $summaryEn ?: null,
        ':summary_ar' => $summaryAr ?: null,
        ':description_en' => $descriptionEn ?: null,
        ':description_ar' => $descriptionAr ?: null,
        ':id' => $id,
    ];

    if ($fileData) {
        $sql .= ', document_type = :document_type, file_name = :file_name, file_path = :file_path, file_size_kb = :file_size_kb';
        $params[':document_type'] = $fileData['document_type'];
        $params[':file_name'] = $fileData['file_name'];
        $params[':file_path'] = $fileData['file_path'];
        $params[':file_size_kb'] = $fileData['file_size_kb'];
    }

    if ($coverPath) {
        $sql .= ', cover_image_path = :cover_image_path';
        $params[':cover_image_path'] = $coverPath;
    }

    $sql .= ' WHERE id = :id';

    $stmt = db()->prepare($sql);
    $stmt->execute($params);
}

function library_delete_document(int $id): void
{
    library_bootstrap();
    // Optionally delete files, but for safety we might keep them or delete them.
    // For now, just delete DB record.
    $stmt = db()->prepare('DELETE FROM library_documents WHERE id = ?');
    $stmt->execute([$id]);
}

function library_render_pagination(int $currentPage, int $totalPages, string $baseUrl): void
{
    if ($totalPages <= 1) {
        return;
    }
    
    $queryParts = parse_url($baseUrl, PHP_URL_QUERY);
    parse_str($queryParts ?? '', $queryParams);
    
    $buildUrl = function(int $page) use ($baseUrl, $queryParams) {
        $queryParams['page'] = $page;
        $path = parse_url($baseUrl, PHP_URL_PATH);
        return $path . '?' . http_build_query($queryParams);
    };

    echo '<nav aria-label="Page navigation" class="mt-4">';
    echo '<ul class="pagination justify-content-center">';
    
    // Previous
    $prevClass = $currentPage <= 1 ? 'disabled' : '';
    echo '<li class="page-item ' . $prevClass . '">';
    echo '<a class="page-link" href="' . h($buildUrl($currentPage - 1)) . '">' . library_trans('previous') . '</a>';
    echo '</li>';
    
    // Numbers
    $start = max(1, $currentPage - 2);
    $end = min($totalPages, $currentPage + 2);
    
    if ($start > 1) {
        echo '<li class="page-item"><a class="page-link" href="' . h($buildUrl(1)) . '">1</a></li>';
        if ($start > 2) echo '<li class="page-item disabled"><span class="page-link">...</span></li>';
    }
    
    for ($i = $start; $i <= $end; $i++) {
        $active = $i === $currentPage ? 'active' : '';
        echo '<li class="page-item ' . $active . '">';
        echo '<a class="page-link" href="' . h($buildUrl($i)) . '">' . $i . '</a>';
        echo '</li>';
    }
    
    if ($end < $totalPages) {
        if ($end < $totalPages - 1) echo '<li class="page-item disabled"><span class="page-link">...</span></li>';
        echo '<li class="page-item"><a class="page-link" href="' . h($buildUrl($totalPages)) . '">' . $totalPages . '</a></li>';
    }
    
    // Next
    $nextClass = $currentPage >= $totalPages ? 'disabled' : '';
    echo '<li class="page-item ' . $nextClass . '">';
    echo '<a class="page-link" href="' . h($buildUrl($currentPage + 1)) . '">' . library_trans('next') . '</a>';
    echo '</li>';
    
    echo '</ul>';
    echo '<div class="text-center text-secondary small mt-2">' . library_trans('showing_page') . ' ' . $currentPage . ' ' . library_trans('of') . ' ' . $totalPages . '</div>';
    echo '</nav>';
}
