<?php
declare(strict_types=1);

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

/* =========================================================
 * Basic helpers
 * =======================================================*/
function brd_ensure_session(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

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

    @session_start();
}

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

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

    throw new RuntimeException('db() helper was not found.');
}

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

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

    return null;
}

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

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

function brd_redirect(string $url, int $code = 302): void
{
    header('Location: ' . $url, true, $code);
    exit;
}

function brd_current_uri(): string
{
    $uri = (string)($_SERVER['REQUEST_URI'] ?? '/');
    return $uri !== '' ? $uri : '/';
}

function brd_lang(): string
{
    if (function_exists('lang')) {
        $lang = strtolower(trim((string)lang()));
        if (in_array($lang, ['uk', 'ru', 'en'], true)) {
            return $lang;
        }
    }

    brd_ensure_session();
    $me = brd_me();

    $lang = strtolower(trim((string)($_SESSION['lang'] ?? ($me['lang'] ?? 'uk'))));
    if (!in_array($lang, ['uk', 'ru', 'en'], true)) {
        $lang = 'uk';
    }

    return $lang;
}

function brd_t(string $uk, string $ru, string $en): string
{
    $lang = brd_lang();
    return $lang === 'ru' ? $ru : ($lang === 'en' ? $en : $uk);
}

function brd_flash_set(string $key, string $type, string $text): void
{
    brd_ensure_session();
    if (!isset($_SESSION['_brd_flash']) || !is_array($_SESSION['_brd_flash'])) {
        $_SESSION['_brd_flash'] = [];
    }
    $_SESSION['_brd_flash'][$key] = ['type' => $type, 'text' => $text];
}

function brd_flash_get(string $key): array
{
    brd_ensure_session();
    $row = $_SESSION['_brd_flash'][$key] ?? null;
    if (isset($_SESSION['_brd_flash'][$key])) {
        unset($_SESSION['_brd_flash'][$key]);
    }

    if (!is_array($row)) {
        return ['', ''];
    }

    return [
        (string)($row['type'] ?? ''),
        (string)($row['text'] ?? ''),
    ];
}

function brd_csrf_token(): string
{
    if (function_exists('csrf_token')) {
        return (string)csrf_token();
    }

    brd_ensure_session();
    $tok = (string)($_SESSION['_brd_csrf'] ?? '');
    if ($tok === '') {
        $tok = bin2hex(random_bytes(32));
        $_SESSION['_brd_csrf'] = $tok;
    }
    return $tok;
}

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

    brd_ensure_session();
    $token = is_string($token) ? trim($token) : '';
    if ($token === '') {
        return false;
    }

    $sessionToken = (string)($_SESSION['_brd_csrf'] ?? '');
    return $sessionToken !== '' && hash_equals($sessionToken, $token);
}

function brd_is_admin(?array $user = null): bool
{
    $user = is_array($user) ? $user : brd_me();

    if (function_exists('is_admin')) {
        try {
            return (bool)is_admin($user);
        } catch (Throwable $e) {
        }
    }

    return is_array($user) && strtolower((string)($user['role'] ?? '')) === 'admin';
}

function brd_require_login(): array
{
    $me = brd_me();
    if (!$me) {
        brd_redirect('/login.php?to=' . urlencode(brd_current_uri()));
    }
    return $me;
}

function brd_require_admin(): array
{
    $me = brd_require_login();
    if (!brd_is_admin($me)) {
        http_response_code(403);
        exit('Forbidden');
    }
    return $me;
}

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

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

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

    $k = 't:' . $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) {
        }
    }

    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();
    } catch (Throwable $e) {
        $cache[$k] = false;
    }

    return (bool)$cache[$k];
}

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

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

    $k = 'c:' . $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) {
        }
    }

    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();
    } catch (Throwable $e) {
        $cache[$k] = false;
    }

    return (bool)$cache[$k];
}

function brd_ready(PDO $pdo): bool
{
    return brd_table_exists($pdo, 'board_ads')
        && brd_table_exists($pdo, 'board_ad_images')
        && brd_table_exists($pdo, 'board_categories')
        && brd_table_exists($pdo, 'board_regions')
        && brd_table_exists($pdo, 'board_ad_viewers')
        && brd_table_exists($pdo, 'board_ad_moderation');
}

/* =========================================================
 * Settings
 * =======================================================*/
function brd_settings_defaults(): array
{
    return [
        'board_enabled' => '1',
        'board_publish_price' => '25',
        'board_items_per_page' => '12',
        'board_default_country' => 'UA',
        'board_require_moderation' => '1',
    ];
}

function brd_load_settings(PDO $pdo): array
{
    $defaults = brd_settings_defaults();

    if (!brd_table_exists($pdo, 'app_settings')) {
        return $defaults;
    }

    $keys = array_keys($defaults);
    $in = implode(',', array_fill(0, count($keys), '?'));

    try {
        $st = $pdo->prepare("SELECT key_name, value_text FROM app_settings WHERE key_name IN ($in)");
        $st->execute($keys);

        $out = $defaults;
        foreach (($st->fetchAll(PDO::FETCH_ASSOC) ?: []) as $row) {
            $key = (string)($row['key_name'] ?? '');
            if ($key !== '' && array_key_exists($key, $out)) {
                $out[$key] = (string)($row['value_text'] ?? '');
            }
        }
        return $out;
    } catch (Throwable $e) {
        return $defaults;
    }
}

function brd_save_settings(PDO $pdo, array $values): void
{
    if (!brd_table_exists($pdo, 'app_settings')) {
        throw new RuntimeException('Table app_settings was not found.');
    }

    $allowed = brd_settings_defaults();
    $payload = [];

    foreach ($allowed as $key => $default) {
        if (array_key_exists($key, $values)) {
            $payload[$key] = (string)$values[$key];
        }
    }

    if (!$payload) {
        return;
    }

    $ownTx = !$pdo->inTransaction();
    if ($ownTx) {
        $pdo->beginTransaction();
    }

    try {
        $st = $pdo->prepare("
            INSERT INTO app_settings (key_name, value_text, updated_at)
            VALUES (?, ?, NOW())
            ON DUPLICATE KEY UPDATE
                value_text = VALUES(value_text),
                updated_at = VALUES(updated_at)
        ");

        foreach ($payload as $key => $value) {
            $st->execute([$key, $value]);
        }

        if ($ownTx) {
            $pdo->commit();
        }
    } catch (Throwable $e) {
        if ($ownTx && $pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $e;
    }
}

/* =========================================================
 * Countries / categories / regions
 * =======================================================*/
function brd_detect_country_code(?array $user = null, ?array $settings = null): string
{
    $user = is_array($user) ? $user : brd_me();
    $settings = is_array($settings) ? $settings : brd_settings_defaults();

    $candidates = [
        strtoupper(trim((string)($user['country_code'] ?? ''))),
        strtoupper(trim((string)($_SERVER['HTTP_CF_IPCOUNTRY'] ?? ''))),
        strtoupper(trim((string)($_SERVER['GEOIP_COUNTRY_CODE'] ?? ''))),
        strtoupper(trim((string)($settings['board_default_country'] ?? 'UA'))),
        'UA',
    ];

    foreach ($candidates as $code) {
        if ($code !== '' && preg_match('/^[A-Z]{2}$/', $code)) {
            return $code;
        }
    }

    return 'UA';
}

function brd_detect_city(?array $user = null): string
{
    $user = is_array($user) ? $user : brd_me();
    $city = trim((string)($user['city'] ?? ''));
    if ($city !== '') {
        return $city;
    }

    return trim((string)($_SERVER['HTTP_CF_IPCITY'] ?? ''));
}

function brd_categories(PDO $pdo): array
{
    $lang = brd_lang();
    $nameCol = $lang === 'ru' ? 'name_ru' : ($lang === 'en' ? 'name_en' : 'name_uk');

    try {
        $st = $pdo->query("
            SELECT id, slug, {$nameCol} AS name
            FROM board_categories
            WHERE is_active = 1
            ORDER BY sort ASC, id ASC
        ");
        return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        return [];
    }
}

function brd_category_map(PDO $pdo): array
{
    $out = [];
    foreach (brd_categories($pdo) as $row) {
        $out[(int)$row['id']] = $row;
    }
    return $out;
}

function brd_regions(PDO $pdo, string $countryCode): array
{
    $countryCode = strtoupper(trim($countryCode));
    $lang = brd_lang();
    $nameCol = $lang === 'ru' ? 'name_ru' : ($lang === 'en' ? 'name_en' : 'name_uk');

    try {
        $st = $pdo->prepare("
            SELECT id, region_code, {$nameCol} AS name
            FROM board_regions
            WHERE country_code = ?
              AND is_active = 1
            ORDER BY sort ASC, id ASC
        ");
        $st->execute([$countryCode]);
        return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        return [];
    }
}

function brd_region_name_by_id(PDO $pdo, int $regionId): string
{
    if ($regionId <= 0) {
        return '';
    }

    $lang = brd_lang();
    $nameCol = $lang === 'ru' ? 'name_ru' : ($lang === 'en' ? 'name_en' : 'name_uk');

    try {
        $st = $pdo->prepare("SELECT {$nameCol} AS name FROM board_regions WHERE id = ? LIMIT 1");
        $st->execute([$regionId]);
        return trim((string)$st->fetchColumn());
    } catch (Throwable $e) {
        return '';
    }
}

/* =========================================================
 * Validation / formatting
 * =======================================================*/
function brd_trimmed(?string $s): string
{
    return trim((string)$s);
}

function brd_price_to_decimal(?string $s): string
{
    $s = str_replace([' ', ','], ['', '.'], trim((string)$s));
    if ($s === '' || !preg_match('/^\d+(\.\d{1,2})?$/', $s)) {
        return '0.00';
    }
    return number_format((float)$s, 2, '.', '');
}

function brd_phone_norm(?string $s): string
{
    $s = trim((string)$s);
    if ($s === '') {
        return '';
    }
    return preg_replace('/[^\d\+\-\(\)\s]/u', '', $s) ?? '';
}

function brd_status_label(string $status): string
{
    return match ($status) {
        'draft' => brd_t('Чернетка', 'Черновик', 'Draft'),
        'pending' => brd_t('На модерації', 'На модерации', 'Pending moderation'),
        'approved' => brd_t('Опубліковано', 'Опубликовано', 'Published'),
        'rejected' => brd_t('Відхилено', 'Отклонено', 'Rejected'),
        'archived' => brd_t('В архіві', 'В архиве', 'Archived'),
        'deleted' => brd_t('Видалено', 'Удалено', 'Deleted'),
        default => $status,
    };
}

function brd_status_badge_class(string $status): string
{
    return match ($status) {
        'approved' => 'success',
        'pending' => 'warning text-dark',
        'rejected' => 'danger',
        'archived' => 'secondary',
        'deleted' => 'dark',
        default => 'light text-dark border',
    };
}

function brd_pagination(int $page, int $perPage): array
{
    $page = max(1, $page);
    $perPage = max(1, min(100, $perPage));
    return [
        'page' => $page,
        'per_page' => $perPage,
        'offset' => ($page - 1) * $perPage,
    ];
}

function brd_ad_url(int $adId): string
{
    return '/functional/board/view.php?id=' . $adId;
}

/* =========================================================
 * Uploads
 * =======================================================*/
function brd_root_path(): string
{
    return dirname(__DIR__, 2);
}

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

function brd_mkdir(string $path): void
{
    if (is_dir($path)) {
        return;
    }
    if (!@mkdir($path, 0775, true) && !is_dir($path)) {
        throw new RuntimeException('Failed to create directory: ' . $path);
    }
}

function brd_ad_dirs(array $user, int $adId): array
{
    $username = brd_safe_username((string)($user['username'] ?? 'user'));
    $baseRel = '/upload/' . $username . '/board/' . $adId;
    $baseFs = brd_root_path() . $baseRel;

    $mainFs = $baseFs . '/main';
    $galleryFs = $baseFs . '/gallery';

    brd_mkdir($mainFs);
    brd_mkdir($galleryFs);

    return [
        'base_rel' => $baseRel,
        'base_fs' => $baseFs,
        'main_fs' => $mainFs,
        'gallery_fs' => $galleryFs,
        'main_rel' => $baseRel . '/main',
        'gallery_rel' => $baseRel . '/gallery',
    ];
}

function brd_store_uploaded_image(array $file, string $targetFsDir, string $targetRelDir): array
{
    if (!isset($file['tmp_name']) || !is_string($file['tmp_name']) || $file['tmp_name'] === '') {
        throw new RuntimeException('Empty upload.');
    }

    if (!is_uploaded_file($file['tmp_name'])) {
        throw new RuntimeException('Upload transport error.');
    }

    $err = (int)($file['error'] ?? UPLOAD_ERR_OK);
    if ($err !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Upload error code: ' . $err);
    }

    $size = (int)($file['size'] ?? 0);
    if ($size <= 0) {
        throw new RuntimeException('Empty file.');
    }

    $mime = '';
    $finfo = @finfo_open(FILEINFO_MIME_TYPE);
    if ($finfo instanceof finfo || is_resource($finfo)) {
        $mime = (string)@finfo_file($finfo, $file['tmp_name']);
        @finfo_close($finfo);
    }

    $img = @getimagesize($file['tmp_name']);
    if (is_array($img) && !empty($img['mime']) && $mime === '') {
        $mime = (string)$img['mime'];
    }

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

    if (!isset($allowed[$mime])) {
        throw new RuntimeException(brd_t(
            'Дозволені лише JPG/JPEG, PNG, WEBP або GIF.',
            'Разрешены только JPG/JPEG, PNG, WEBP или GIF.',
            'Only JPG/JPEG, PNG, WEBP or GIF files are allowed.'
        ));
    }

    if (!is_array($img) || empty($img[0]) || empty($img[1])) {
        throw new RuntimeException('The uploaded file is not a valid image.');
    }

    brd_mkdir($targetFsDir);

    $ext = $allowed[$mime];
    $stored = date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
    $destFs = rtrim($targetFsDir, '/\\') . DIRECTORY_SEPARATOR . $stored;

    if (!@move_uploaded_file($file['tmp_name'], $destFs)) {
        throw new RuntimeException('Failed to move uploaded file.');
    }

    return [
        'rel_path' => rtrim($targetRelDir, '/') . '/' . $stored,
        'stored_name' => $stored,
        'original_name' => (string)($file['name'] ?? $stored),
        'mime' => $mime,
        'size_bytes' => $size,
        'width' => (int)$img[0],
        'height' => (int)$img[1],
    ];
}

function brd_delete_rel_file(?string $relPath): void
{
    $relPath = trim((string)$relPath);
    if ($relPath === '' || strpos($relPath, '/upload/') !== 0) {
        return;
    }

    $full = brd_root_path() . $relPath;
    if (is_file($full)) {
        @unlink($full);
    }
}



function brd_normalize_files_array(array $files): array
{
    $out = [];
    $names = $files['name'] ?? [];
    if (!is_array($names)) {
        return $out;
    }

    foreach (array_keys($names) as $idx) {
        $out[] = [
            'name' => $files['name'][$idx] ?? '',
            'type' => $files['type'][$idx] ?? '',
            'tmp_name' => $files['tmp_name'][$idx] ?? '',
            'error' => $files['error'][$idx] ?? UPLOAD_ERR_NO_FILE,
            'size' => $files['size'][$idx] ?? 0,
        ];
    }

    return $out;
}

/* =========================================================
 * Ads / moderation / views
 * =======================================================*/
function brd_log_moderation(PDO $pdo, int $adId, ?int $adminUserId, string $action, ?string $note = null): void
{
    try {
        $st = $pdo->prepare("
            INSERT INTO board_ad_moderation
                (ad_id, admin_user_id, action, note, created_at)
            VALUES (?, ?, ?, ?, NOW())
        ");
        $st->execute([
            $adId,
            $adminUserId > 0 ? $adminUserId : null,
            $action,
            $note !== null && trim($note) !== '' ? trim($note) : null,
        ]);
    } catch (Throwable $e) {
    }
}

function brd_charge_user_coins(
    PDO $pdo,
    int $userId,
    int $amount,
    string $entityType,
    int $entityId,
    string $eventKey,
    string $note,
    array $meta = []
): array {
    if ($amount <= 0) {
        return ['ok' => true, 'balance_after' => 0];
    }

    if (!brd_column_exists($pdo, 'users', 'vm_coins_balance') || !brd_table_exists($pdo, 'vm_coin_transactions')) {
        return [
            'ok' => false,
            'message' => brd_t(
                'У БД не знайдено таблицю/колонку VM Coins.',
                'В БД не найдена таблица/колонка VM Coins.',
                'VM Coins table/column was not found in the database.'
            ),
        ];
    }

    $ownTx = !$pdo->inTransaction();
    if ($ownTx) {
        $pdo->beginTransaction();
    }

    try {
        $st = $pdo->prepare("
            SELECT vm_coins_balance, vm_coins_spent_total
            FROM users
            WHERE id = ?
            FOR UPDATE
        ");
        $st->execute([$userId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            throw new RuntimeException('User not found.');
        }

        $balance = (int)($row['vm_coins_balance'] ?? 0);
        if ($balance < $amount) {
            if ($ownTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            return [
                'ok' => false,
                'message' => brd_t(
                    'Недостатньо VM Coins для публікації оголошення.',
                    'Недостаточно VM Coins для публикации объявления.',
                    'Not enough VM Coins to publish the ad.'
                ),
                'balance' => $balance,
            ];
        }

        $newBalance = $balance - $amount;
        $spentTotal = (int)($row['vm_coins_spent_total'] ?? 0) + $amount;

        $up = $pdo->prepare("
            UPDATE users
            SET vm_coins_balance = ?,
                vm_coins_spent_total = ?,
                vm_coins_updated_at = NOW()
            WHERE id = ?
        ");
        $up->execute([$newBalance, $spentTotal, $userId]);

        $ins = $pdo->prepare("
            INSERT INTO vm_coin_transactions
                (user_id, delta_amount, balance_after, event_key, entity_type, entity_id, op_key, note, meta_json, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
        ");
        $opKey = 'board:' . $eventKey . ':' . $userId . ':' . $entityId;
        $ins->execute([
            $userId,
            -$amount,
            $newBalance,
            $eventKey,
            $entityType,
            $entityId,
            $opKey,
            $note,
            $meta ? json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null,
        ]);

        if ($ownTx) {
            $pdo->commit();
        }

        return ['ok' => true, 'balance_after' => $newBalance];
    } catch (Throwable $e) {
        if ($ownTx && $pdo->inTransaction()) {
            $pdo->rollBack();
        }

        return ['ok' => false, 'message' => $e->getMessage()];
    }
}

function brd_viewer_key(?array $me = null): string
{
    $me = is_array($me) ? $me : brd_me();
    if ($me && (int)($me['id'] ?? 0) > 0) {
        return hash('sha256', 'u:' . (int)$me['id']);
    }

    brd_ensure_session();

    if (empty($_COOKIE['vm_board_viewer'])) {
        $token = bin2hex(random_bytes(24));
        setcookie('vm_board_viewer', $token, time() + 86400 * 365, '/');
        $_COOKIE['vm_board_viewer'] = $token;
    }

    return hash('sha256', 'g:' . (string)$_COOKIE['vm_board_viewer']);
}

function brd_record_view(PDO $pdo, int $adId, ?array $me = null): void
{
    if ($adId <= 0) {
        return;
    }

    $me = is_array($me) ? $me : brd_me();
    $viewerUserId = (int)($me['id'] ?? 0);
    $viewerKey = brd_viewer_key($me);
    $countryCode = brd_detect_country_code($me);
    $city = brd_detect_city($me);

    $ownTx = !$pdo->inTransaction();
    if ($ownTx) {
        $pdo->beginTransaction();
    }

    try {
        $up = $pdo->prepare("UPDATE board_ads SET total_views = total_views + 1 WHERE id = ?");
        $up->execute([$adId]);

        $ins = $pdo->prepare("
            INSERT INTO board_ad_viewers
                (ad_id, viewer_key, viewer_user_id, country_code, city_name, first_viewed_at, last_viewed_at)
            VALUES (?, ?, ?, ?, ?, NOW(), NOW())
            ON DUPLICATE KEY UPDATE
                viewer_user_id = VALUES(viewer_user_id),
                country_code = VALUES(country_code),
                city_name = VALUES(city_name),
                last_viewed_at = VALUES(last_viewed_at)
        ");
        $ins->execute([
            $adId,
            $viewerKey,
            $viewerUserId > 0 ? $viewerUserId : null,
            $countryCode !== '' ? $countryCode : null,
            $city !== '' ? $city : null,
        ]);

        if ($ins->rowCount() === 1) {
            $up2 = $pdo->prepare("UPDATE board_ads SET unique_views = unique_views + 1 WHERE id = ?");
            $up2->execute([$adId]);
        }

        if ($ownTx) {
            $pdo->commit();
        }
    } catch (Throwable $e) {
        if ($ownTx && $pdo->inTransaction()) {
            $pdo->rollBack();
        }
    }
}

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

    $lang = brd_lang();
    $catNameExpr = $lang === 'ru' ? 'c.name_ru' : ($lang === 'en' ? 'c.name_en' : 'c.name_uk');

    $sql = "
        SELECT
            a.*,
            u.username AS author_username,
            u.full_name AS author_full_name,
            u.first_name AS author_first_name,
            u.last_name AS author_last_name,
            u.avatar_path AS author_avatar_path,
            {$catNameExpr} AS category_name,
            c.slug AS category_slug
        FROM board_ads a
        INNER JOIN users u ON u.id = a.user_id
        LEFT JOIN board_categories c ON c.id = a.category_id
        WHERE a.id = ?
        LIMIT 1
    ";

    try {
        $st = $pdo->prepare($sql);
        $st->execute([$adId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return is_array($row) ? $row : null;
    } catch (Throwable $e) {
        return null;
    }
}

function brd_ad_images(PDO $pdo, int $adId): array
{
    if ($adId <= 0) {
        return [];
    }

    try {
        $st = $pdo->prepare("
            SELECT *
            FROM board_ad_images
            WHERE ad_id = ?
            ORDER BY sort_order ASC, id ASC
        ");
        $st->execute([$adId]);
        return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        return [];
    }
}

function brd_count_additional_images(PDO $pdo, int $adId): int
{
    try {
        $st = $pdo->prepare("SELECT COUNT(*) FROM board_ad_images WHERE ad_id = ?");
        $st->execute([$adId]);
        return (int)$st->fetchColumn();
    } catch (Throwable $e) {
        return 0;
    }
}

function brd_owner_can_edit(array $ad, ?array $me = null): bool
{
    $me = is_array($me) ? $me : brd_me();
    $meId = (int)($me['id'] ?? 0);
    if ($meId <= 0) {
        return false;
    }
    return $meId === (int)($ad['user_id'] ?? 0) || brd_is_admin($me);
}

function brd_can_view_ad(array $ad, ?array $me = null): bool
{
    $status = (string)($ad['status'] ?? '');
    if ($status === 'approved') {
        return true;
    }
    return brd_owner_can_edit($ad, $me);
}

function brd_public_display_name(array $ad): string
{
    $full = trim((string)($ad['author_full_name'] ?? ''));
    if ($full !== '') {
        return $full;
    }

    $first = trim((string)($ad['author_first_name'] ?? ''));
    $last = trim((string)($ad['author_last_name'] ?? ''));
    $name = trim($first . ' ' . $last);
    if ($name !== '') {
        return $name;
    }

    return (string)($ad['author_username'] ?? 'User');
}

function brd_profile_url(string $username): string
{
    return '/profile/?u=' . rawurlencode($username);
}
