<?php
declare(strict_types=1);

/**
 * /functional/albums/_lib.php
 * Спільні helper-и для модуля фотоальбомів:
 * - DB/session/html helper-и
 * - i18n helper
 * - перевірка schema/photo_albums/album_photos
 * - права доступу до альбомів
 * - helper-и для user profile privacy
 * - upload helper для фото
 * - helper-и для отримання альбомів/фото/cover
 */

if (defined('VM_ALBUMS_LIB_LOADED')) {
    return;
}
define('VM_ALBUMS_LIB_LOADED', true);

/* =========================================================
 * Base helpers
 * ======================================================= */

function alb_ensure_session(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    if (function_exists('ensure_session_started')) {
        try {
            ensure_session_started();
            return;
        } catch (Throwable $e) {
            // fallback below
        }
    }

    @session_start();
}

function alb_db(): PDO
{
    if (function_exists('db')) {
        return db();
    }

    if (isset($GLOBALS['pdo']) && $GLOBALS['pdo'] instanceof PDO) {
        return $GLOBALS['pdo'];
    }

    throw new RuntimeException('db() not found. Ensure includes/start.php is loaded.');
}

function alb_me(): ?array
{
    if (function_exists('current_user')) {
        $me = current_user();
        return is_array($me) ? $me : null;
    }

    if (isset($GLOBALS['me']) && is_array($GLOBALS['me'])) {
        return $GLOBALS['me'];
    }

    return null;
}

function alb_h(?string $s): string
{
    if (function_exists('h')) {
        return h($s);
    }

    return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function alb_lang(): string
{
    $lang = '';

    if (function_exists('lang')) {
        $lang = (string)lang();
    } else {
        alb_ensure_session();
        $lang = (string)($_SESSION['lang'] ?? 'uk');
    }

    if (!in_array($lang, ['uk', 'ru', 'en'], true)) {
        $lang = 'uk';
    }

    return $lang;
}

function alb_t(string $uk, string $ru, string $en): string
{
    return match (alb_lang()) {
        'ru' => $ru,
        'en' => $en,
        default => $uk,
    };
}

function alb_current_user_id(?array $me = null): int
{
    $me = is_array($me) ? $me : alb_me();
    return (int)($me['id'] ?? 0);
}

function alb_is_admin(?array $me = null): bool
{
    $me = is_array($me) ? $me : alb_me();
    if (!$me) {
        return false;
    }

    if (function_exists('is_admin')) {
        try {
            return (bool)is_admin($me);
        } catch (Throwable $e) {
            // fallback below
        }
    }

    return ((string)($me['role'] ?? '') === 'admin');
}

function alb_norm_path(string $p): string
{
    $p = str_replace('\\', '/', $p);
    $p = preg_replace('~/+~', '/', $p) ?? $p;
    return rtrim($p, '/');
}

function alb_ident_ok(string $name): bool
{
    return (bool)preg_match('/^[a-zA-Z0-9_]+$/', $name);
}

/* =========================================================
 * CSRF
 * ======================================================= */

function alb_csrf_token(): string
{
    if (function_exists('vm_csrf_token')) {
        return (string)vm_csrf_token();
    }

    if (function_exists('csrf_token')) {
        return (string)csrf_token();
    }

    alb_ensure_session();

    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
    }

    return (string)$_SESSION['csrf_token'];
}

function alb_csrf_check(?string $token): bool
{
    if (function_exists('vm_csrf_check')) {
        return (bool)vm_csrf_check($token);
    }

    if (function_exists('csrf_check')) {
        return (bool)csrf_check($token);
    }

    alb_ensure_session();

    $sess = (string)($_SESSION['csrf_token'] ?? '');
    $sent = (string)($token ?? '');

    return $sess !== '' && hash_equals($sess, $sent);
}

/* =========================================================
 * DB schema helpers
 * ======================================================= */

function alb_table_exists(PDO $pdo, string $table): bool
{
    static $cache = [];

    if (!alb_ident_ok($table)) {
        return false;
    }

    $k = strtolower($table);
    if (array_key_exists($k, $cache)) {
        return (bool)$cache[$k];
    }

    if (function_exists('db_has_table')) {
        try {
            $cache[$k] = (bool)db_has_table($pdo, $table);
            return (bool)$cache[$k];
        } catch (Throwable $e) {
            // fallback below
        }
    }

    try {
        $st = $pdo->prepare("
            SELECT 1
            FROM information_schema.tables
            WHERE table_schema = DATABASE()
              AND table_name = ?
            LIMIT 1
        ");
        $st->execute([$table]);
        $cache[$k] = (bool)$st->fetchColumn();
        return (bool)$cache[$k];
    } catch (Throwable $e) {
        $cache[$k] = false;
        return false;
    }
}

function alb_has_column(PDO $pdo, string $table, string $column): bool
{
    static $cache = [];

    if (!alb_ident_ok($table) || !alb_ident_ok($column)) {
        return false;
    }

    $k = strtolower($table . '.' . $column);
    if (array_key_exists($k, $cache)) {
        return (bool)$cache[$k];
    }

    if (function_exists('db_has_column')) {
        try {
            $cache[$k] = (bool)db_has_column($pdo, $table, $column);
            return (bool)$cache[$k];
        } catch (Throwable $e) {
            // fallback below
        }
    }

    try {
        $st = $pdo->prepare("
            SELECT 1
            FROM information_schema.columns
            WHERE table_schema = DATABASE()
              AND table_name = ?
              AND column_name = ?
            LIMIT 1
        ");
        $st->execute([$table, $column]);
        $cache[$k] = (bool)$st->fetchColumn();
        return (bool)$cache[$k];
    } catch (Throwable $e) {
        $cache[$k] = false;
        return false;
    }
}

function alb_ready(PDO $pdo): bool
{
    return alb_table_exists($pdo, 'photo_albums')
        && alb_table_exists($pdo, 'album_photos');
}

/* =========================================================
 * User/profile/privacy helpers
 * ======================================================= */

function alb_user_get(PDO $pdo, int $userId): ?array
{
    if ($userId <= 0) {
        return null;
    }

    try {
        $st = $pdo->prepare("SELECT * FROM users WHERE id = ? LIMIT 1");
        $st->execute([$userId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable $e) {
        return null;
    }
}

function alb_user_display_name(array $user): string
{
    $fullName = trim((string)($user['full_name'] ?? ''));
    if ($fullName !== '') {
        return $fullName;
    }

    $first = trim((string)($user['first_name'] ?? ''));
    $last  = trim((string)($user['last_name'] ?? ''));
    $name  = trim($first . ' ' . $last);

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

    return trim((string)($user['username'] ?? ''));
}

function alb_profile_privacy_column(PDO $pdo): string
{
    static $cache = null;

    if (is_string($cache)) {
        return $cache;
    }

    $cache = '';
    $candidates = [
        'is_private',
        'private_profile',
        'profile_private',
        'closed_profile',
        'is_closed',
        'is_profile_private',
        'private',
    ];

    foreach ($candidates as $col) {
        if (alb_has_column($pdo, 'users', $col)) {
            $cache = $col;
            break;
        }
    }

    return $cache;
}

function alb_is_user_profile_private(PDO $pdo, array $user): bool
{
    $col = alb_profile_privacy_column($pdo);
    if ($col === '') {
        return false;
    }

    return ((int)($user[$col] ?? 0) === 1);
}

function alb_are_friends(PDO $pdo, int $userA, int $userB): bool
{
    if ($userA <= 0 || $userB <= 0) {
        return false;
    }

    if ($userA === $userB) {
        return true;
    }

    if (function_exists('vm_are_friends')) {
        try {
            return (bool)vm_are_friends($pdo, $userA, $userB);
        } catch (Throwable $e) {
            // fallback below
        }
    }

    if (!alb_table_exists($pdo, 'friend_requests')) {
        return false;
    }

    try {
        $statusCond = alb_has_column($pdo, 'friend_requests', 'status')
            ? " AND status IN ('accepted','active') "
            : '';

        $st = $pdo->prepare("
            SELECT 1
            FROM friend_requests
            WHERE (
                    (from_user_id = ? AND to_user_id = ?)
                 OR (from_user_id = ? AND to_user_id = ?)
                  )
                  {$statusCond}
            LIMIT 1
        ");
        $st->execute([$userA, $userB, $userB, $userA]);

        return (bool)$st->fetchColumn();
    } catch (Throwable $e) {
        return false;
    }
}

function alb_can_view_owner_profile(PDO $pdo, array $ownerUser, ?array $viewer = null): bool
{
    $viewer   = is_array($viewer) ? $viewer : alb_me();
    $viewerId = (int)($viewer['id'] ?? 0);
    $ownerId  = (int)($ownerUser['id'] ?? 0);

    if ($ownerId <= 0) {
        return false;
    }

    if ($viewerId > 0 && $viewerId === $ownerId) {
        return true;
    }

    if (alb_is_admin($viewer)) {
        return true;
    }

    if (!alb_is_user_profile_private($pdo, $ownerUser)) {
        return true;
    }

    return alb_are_friends($pdo, $viewerId, $ownerId);
}

function alb_resolve_profile_media_url(string $raw, string $username, string $type): string
{
    $raw = trim($raw);
    $username = trim($username);
    $type = ($type === 'cover') ? 'cover' : 'avatar';

    if ($username === '') {
        return '';
    }

    $baseUrl = '/upload/' . $username . '/' . $type . '/';

    if ($raw !== '') {
        if (str_contains($raw, '..')) {
            return '';
        }
        if (preg_match('~^https?://~i', $raw)) {
            return $raw;
        }
        if (str_starts_with($raw, '/')) {
            return $raw;
        }
        if (preg_match('~^upload/~i', $raw) || preg_match('~^uploads/~i', $raw)) {
            return '/' . ltrim($raw, '/');
        }

        $fn = basename($raw);
        if ($fn === '' || $fn === '.' || $fn === '..') {
            return '';
        }

        return $baseUrl . $fn;
    }

    return '';
}

function alb_user_avatar_url(array $user): string
{
    return alb_resolve_profile_media_url(
        (string)($user['avatar_path'] ?? ''),
        (string)($user['username'] ?? ''),
        'avatar'
    );
}

/* =========================================================
 * Album privacy
 * ======================================================= */

function alb_privacy_normalize(?string $privacy): string
{
    $privacy = trim((string)$privacy);
    return in_array($privacy, ['public', 'friends', 'private'], true) ? $privacy : 'public';
}

function alb_privacy_label(string $privacy): string
{
    return match (alb_privacy_normalize($privacy)) {
        'friends' => alb_t('Тільки друзі', 'Только друзья', 'Friends only'),
        'private' => alb_t('Тільки я', 'Только я', 'Only me'),
        default   => alb_t('Усі', 'Все', 'Public'),
    };
}

function alb_privacy_options(): array
{
    return [
        'public'  => alb_privacy_label('public'),
        'friends' => alb_privacy_label('friends'),
        'private' => alb_privacy_label('private'),
    ];
}

function alb_can_manage_album(array $album, ?array $viewer = null): bool
{
    $viewer   = is_array($viewer) ? $viewer : alb_me();
    $viewerId = (int)($viewer['id'] ?? 0);
    $ownerId  = (int)($album['user_id'] ?? 0);

    if ($viewerId <= 0 || $ownerId <= 0) {
        return false;
    }

    return $viewerId === $ownerId || alb_is_admin($viewer);
}

function alb_can_view_album(PDO $pdo, array $album, ?array $ownerUser = null, ?array $viewer = null): bool
{
    $viewer   = is_array($viewer) ? $viewer : alb_me();
    $viewerId = (int)($viewer['id'] ?? 0);
    $ownerId  = (int)($album['user_id'] ?? 0);

    if ($ownerId <= 0) {
        return false;
    }

    if (!is_array($ownerUser)) {
        $ownerUser = alb_user_get($pdo, $ownerId);
    }
    if (!$ownerUser) {
        return false;
    }

    if ($viewerId > 0 && $viewerId === $ownerId) {
        return true;
    }

    if (alb_is_admin($viewer)) {
        return true;
    }

    if (!alb_can_view_owner_profile($pdo, $ownerUser, $viewer)) {
        return false;
    }

    $privacy = alb_privacy_normalize((string)($album['privacy'] ?? 'public'));

    return match ($privacy) {
        'private' => false,
        'friends' => alb_are_friends($pdo, $viewerId, $ownerId),
        default   => true,
    };
}

function alb_safe_back_url(string $back, string $fallback): string
{
    $back = trim($back);
    if ($back === '') {
        return $fallback;
    }

    if (str_contains($back, "
") || str_contains($back, "
")) {
        return $fallback;
    }

    if (!str_starts_with($back, '/')) {
        return $fallback;
    }

    return $back;
}

function alb_append_query(string $url, array $params): string
{
    $url = trim($url);
    if ($url === '') {
        $url = '/profile/index.php?tab=albums';
    }

    $fragment = '';
    if (str_contains($url, '#')) {
        [$url, $fragment] = explode('#', $url, 2);
        $fragment = '#' . $fragment;
    }

    $path = $url;
    $query = '';

    if (str_contains($url, '?')) {
        [$path, $query] = explode('?', $url, 2);
    }

    $current = [];
    if ($query !== '') {
        parse_str($query, $current);
        if (!is_array($current)) {
            $current = [];
        }
    }

    foreach ($params as $k => $v) {
        $current[(string)$k] = $v;
    }

    $qs = http_build_query($current);
    return $path . ($qs !== '' ? ('?' . $qs) : '') . $fragment;
}

/* =========================================================
 * Upload paths / files
 * ======================================================= */

function alb_upload_root_abs(): string
{
    $p = realpath(__DIR__ . '/../../upload');
    return $p ?: (__DIR__ . '/../../upload');
}

function alb_safe_username(string $username): string
{
    $username = trim($username);
    $username = preg_replace('~[^a-zA-Z0-9_-]+~', '_', $username) ?? '';
    return $username !== '' ? $username : 'user';
}

function alb_user_albums_root_abs(string $username): string
{
    $safeUsername = alb_safe_username($username);
    return rtrim(alb_upload_root_abs(), '/') . '/' . $safeUsername . '/albums';
}

function alb_album_dir_abs(string $username, int $albumId): string
{
    $albumId = max(1, $albumId);
    return rtrim(alb_user_albums_root_abs($username), '/') . '/' . $albumId;
}

function alb_move_uploaded_image(array $file, string $destDirAbs, int $maxBytes = 10485760): array
{
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Upload error: ' . (int)($file['error'] ?? -1));
    }

    $tmp = (string)($file['tmp_name'] ?? '');
    if ($tmp === '' || !is_uploaded_file($tmp)) {
        throw new RuntimeException('Not an uploaded file.');
    }

    $orig = (string)($file['name'] ?? 'file');
    $size = (int)($file['size'] ?? 0);

    if ($size <= 0) {
        throw new RuntimeException('Empty file.');
    }
    if ($maxBytes > 0 && $size > $maxBytes) {
        throw new RuntimeException('File too large.');
    }

    $width = null;
    $height = null;

    if (function_exists('getimagesize')) {
        $gi = @getimagesize($tmp);
        if (!$gi || !is_array($gi)) {
            throw new RuntimeException('Invalid image.');
        }

        $width  = isset($gi[0]) ? (int)$gi[0] : null;
        $height = isset($gi[1]) ? (int)$gi[1] : null;
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = (string)$finfo->file($tmp);

    $allowed = [
        'image/jpeg' => 'jpg',
        'image/png'  => 'png',
        'image/webp' => 'webp',
        'image/gif'  => 'gif',
    ];

    if (!isset($allowed[$mime])) {
        throw new RuntimeException('Only JPG/PNG/WEBP/GIF allowed.');
    }

    if (!is_dir($destDirAbs) && !mkdir($destDirAbs, 0775, true) && !is_dir($destDirAbs)) {
        throw new RuntimeException('Failed to create directory: ' . $destDirAbs);
    }

    $ext = $allowed[$mime];
    $name = date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;

    $destAbs = rtrim($destDirAbs, '/') . '/' . $name;
    if (!move_uploaded_file($tmp, $destAbs)) {
        throw new RuntimeException('Failed to move uploaded file.');
    }

    @chmod($destAbs, 0644);

    $uploadAbs = realpath(alb_upload_root_abs()) ?: alb_upload_root_abs();
    $uploadAbs = alb_norm_path($uploadAbs);

    $destReal = realpath($destAbs) ?: $destAbs;
    $destReal = alb_norm_path($destReal);

    if ($uploadAbs !== '' && str_starts_with($destReal, $uploadAbs)) {
        $rel = substr($destReal, strlen($uploadAbs));
        $rel = ltrim((string)$rel, '/');
        $urlPath = '/upload/' . $rel;
    } else {
        $urlPath = str_replace($uploadAbs, '/upload', $destReal);
        $urlPath = str_replace('\\', '/', $urlPath);
    }

    $urlPath = preg_replace('~/+~', '/', $urlPath) ?? $urlPath;

    return [$urlPath, $mime, $size, $orig, $width, $height];
}

function alb_safe_unlink_under_upload(string $absPath): bool
{
    $root = alb_norm_path(realpath(alb_upload_root_abs()) ?: alb_upload_root_abs());
    $abs  = alb_norm_path(realpath($absPath) ?: $absPath);

    if ($root === '' || $abs === '' || !str_starts_with($abs, $root)) {
        return false;
    }

    if (!is_file($abs)) {
        return false;
    }

    return @unlink($abs);
}

function alb_remove_tree(string $dirAbs): bool
{
    $root = alb_norm_path(realpath(alb_upload_root_abs()) ?: alb_upload_root_abs());
    $dir  = alb_norm_path(realpath($dirAbs) ?: $dirAbs);

    if ($root === '' || $dir === '' || !str_starts_with($dir, $root) || !is_dir($dir)) {
        return false;
    }

    $items = @scandir($dir);
    if (!is_array($items)) {
        return false;
    }

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }

        $path = $dir . '/' . $item;

        if (is_dir($path) && !is_link($path)) {
            alb_remove_tree($path);
        } else {
            @unlink($path);
        }
    }

    return @rmdir($dir);
}

/* =========================================================
 * Data cleanup helpers
 * ======================================================= */

function alb_clean_text(?string $value, int $maxChars = 0): string
{
    $value = trim((string)$value);
    $value = str_replace(["\r\n", "\r"], "\n", $value);

    if ($maxChars > 0) {
        if (function_exists('mb_substr')) {
            $value = (string)mb_substr($value, 0, $maxChars, 'UTF-8');
        } else {
            $value = substr($value, 0, $maxChars);
        }
        $value = trim($value);
    }

    return $value;
}

function alb_clean_title(?string $title): string
{
    return alb_clean_text($title, 190);
}

function alb_clean_description(?string $description): string
{
    return alb_clean_text($description, 5000);
}

function alb_clean_caption(?string $caption): string
{
    return alb_clean_text($caption, 1000);
}

/* =========================================================
 * Album/photo getters
 * ======================================================= */

function alb_album_photo_count(PDO $pdo, int $albumId): int
{
    if ($albumId <= 0 || !alb_table_exists($pdo, 'album_photos')) {
        return 0;
    }

    try {
        $st = $pdo->prepare("SELECT COUNT(*) FROM album_photos WHERE album_id = ?");
        $st->execute([$albumId]);
        return (int)$st->fetchColumn();
    } catch (Throwable $e) {
        return 0;
    }
}

function alb_photo_get(PDO $pdo, int $photoId): ?array
{
    if ($photoId <= 0 || !alb_table_exists($pdo, 'album_photos')) {
        return null;
    }

    try {
        $st = $pdo->prepare("SELECT * FROM album_photos WHERE id = ? LIMIT 1");
        $st->execute([$photoId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable $e) {
        return null;
    }
}

function alb_album_first_photo(PDO $pdo, int $albumId): ?array
{
    if ($albumId <= 0 || !alb_table_exists($pdo, 'album_photos')) {
        return null;
    }

    try {
        $st = $pdo->prepare("
            SELECT *
            FROM album_photos
            WHERE album_id = ?
            ORDER BY sort_order ASC, id ASC
            LIMIT 1
        ");
        $st->execute([$albumId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable $e) {
        return null;
    }
}

function alb_album_get(PDO $pdo, int $albumId): ?array
{
    if ($albumId <= 0 || !alb_ready($pdo)) {
        return null;
    }

    try {
        $hasUpdatedAt = alb_has_column($pdo, 'photo_albums', 'updated_at');

        $sql = "
            SELECT
                a.*,
                (
                    SELECT COUNT(*)
                    FROM album_photos ap
                    WHERE ap.album_id = a.id
                ) AS photos_count
            FROM photo_albums a
            WHERE a.id = ?
            LIMIT 1
        ";

        $st = $pdo->prepare($sql);
        $st->execute([$albumId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            return null;
        }

        if (!$hasUpdatedAt && !isset($row['updated_at'])) {
            $row['updated_at'] = (string)($row['created_at'] ?? '');
        }

        return $row;
    } catch (Throwable $e) {
        return null;
    }
}

function alb_album_cover_photo(PDO $pdo, array $album): ?array
{
    $coverPhotoId = (int)($album['cover_photo_id'] ?? 0);
    if ($coverPhotoId > 0) {
        $photo = alb_photo_get($pdo, $coverPhotoId);
        if ($photo && (int)($photo['album_id'] ?? 0) === (int)($album['id'] ?? 0)) {
            return $photo;
        }
    }

    return alb_album_first_photo($pdo, (int)($album['id'] ?? 0));
}

function alb_album_cover_url(PDO $pdo, array $album): string
{
    $photo = alb_album_cover_photo($pdo, $album);
    if (!$photo) {
        return '';
    }

    $path = trim((string)($photo['file_path'] ?? ''));
    if ($path === '' || str_contains($path, '..')) {
        return '';
    }

    if (preg_match('~^https?://~i', $path)) {
        return $path;
    }

    if (str_starts_with($path, '/')) {
        return $path;
    }

    if (preg_match('~^upload/~i', $path) || preg_match('~^uploads/~i', $path)) {
        return '/' . ltrim($path, '/');
    }

    return $path;
}

function alb_list_user_albums(PDO $pdo, int $ownerUserId, ?array $viewer = null, int $limit = 24, int $offset = 0): array
{
    if ($ownerUserId <= 0 || !alb_ready($pdo)) {
        return [];
    }

    $limit  = max(1, min(100, $limit));
    $offset = max(0, $offset);

    $owner = alb_user_get($pdo, $ownerUserId);
    if (!$owner) {
        return [];
    }

    try {
        $scanLimit = min(200, max($limit + $offset + 20, 60));

        $st = $pdo->prepare("
            SELECT
                a.*,
                (
                    SELECT COUNT(*)
                    FROM album_photos ap
                    WHERE ap.album_id = a.id
                ) AS photos_count
            FROM photo_albums a
            WHERE a.user_id = ?
            ORDER BY a.updated_at DESC, a.id DESC
            LIMIT {$scanLimit}
        ");
        $st->execute([$ownerUserId]);
        $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        return [];
    }

    $visible = [];
    foreach ($rows as $row) {
        if (!alb_can_view_album($pdo, $row, $owner, $viewer)) {
            continue;
        }

        $row['photos_count'] = (int)($row['photos_count'] ?? 0);
        $row['cover_url']    = alb_album_cover_url($pdo, $row);
        $visible[] = $row;
    }

    if ($offset > 0) {
        $visible = array_slice($visible, $offset);
    }

    return array_slice($visible, 0, $limit);
}

function alb_list_album_photos(PDO $pdo, int $albumId, int $limit = 500, int $offset = 0): array
{
    if ($albumId <= 0 || !alb_table_exists($pdo, 'album_photos')) {
        return [];
    }

    $limit  = max(1, min(1000, $limit));
    $offset = max(0, $offset);

    try {
        $st = $pdo->prepare("
            SELECT *
            FROM album_photos
            WHERE album_id = ?
            ORDER BY sort_order ASC, id ASC
            LIMIT {$limit} OFFSET {$offset}
        ");
        $st->execute([$albumId]);
        return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        return [];
    }
}

function alb_update_album(PDO $pdo, int $albumId, string $title, string $description, string $privacy): bool
{
    if ($albumId <= 0 || !alb_table_exists($pdo, 'photo_albums')) {
        return false;
    }

    $title = alb_clean_title($title);
    $description = alb_clean_description($description);
    $privacy = alb_privacy_normalize($privacy);

    if ($title === '') {
        return false;
    }

    try {
        if (alb_has_column($pdo, 'photo_albums', 'updated_at')) {
            $st = $pdo->prepare("
                UPDATE photo_albums
                SET title = ?, description = ?, privacy = ?, updated_at = NOW()
                WHERE id = ?
                LIMIT 1
            ");
            return $st->execute([$title, $description, $privacy, $albumId]);
        }

        $st = $pdo->prepare("
            UPDATE photo_albums
            SET title = ?, description = ?, privacy = ?
            WHERE id = ?
            LIMIT 1
        ");
        return $st->execute([$title, $description, $privacy, $albumId]);
    } catch (Throwable $e) {
        return false;
    }
}

function alb_set_album_cover(PDO $pdo, int $albumId, int $photoId): bool
{
    if ($albumId <= 0 || $photoId <= 0 || !alb_ready($pdo)) {
        return false;
    }

    $photo = alb_photo_get($pdo, $photoId);
    if (!$photo || (int)($photo['album_id'] ?? 0) !== $albumId) {
        return false;
    }

    try {
        if (alb_has_column($pdo, 'photo_albums', 'updated_at')) {
            $st = $pdo->prepare("
                UPDATE photo_albums
                SET cover_photo_id = ?, updated_at = NOW()
                WHERE id = ?
                LIMIT 1
            ");
            return $st->execute([$photoId, $albumId]);
        }

        $st = $pdo->prepare("
            UPDATE photo_albums
            SET cover_photo_id = ?
            WHERE id = ?
            LIMIT 1
        ");
        return $st->execute([$photoId, $albumId]);
    } catch (Throwable $e) {
        return false;
    }
}

/* =========================================================
 * URL helpers
 * ======================================================= */

function alb_album_url(array $album, ?array $ownerUser = null): string
{
    $albumId = (int)($album['id'] ?? 0);
    if ($albumId <= 0) {
        return '/profile/index.php?tab=albums';
    }

    $params = ['album_id' => $albumId];

    if (is_array($ownerUser)) {
        $username = trim((string)($ownerUser['username'] ?? ''));
        if ($username !== '') {
            $params['u'] = $username;
        }
    }

    return '/functional/albums/view.php?' . http_build_query($params);
}

function alb_profile_albums_url(array $ownerUser): string
{
    $username = trim((string)($ownerUser['username'] ?? ''));
    $userId   = (int)($ownerUser['id'] ?? 0);

    $params = ['tab' => 'albums'];

    if ($username !== '') {
        $params['u'] = $username;
    } elseif ($userId > 0) {
        $params['id'] = $userId;
    }

    return '/profile/index.php?' . http_build_query($params);
}