<?php
declare(strict_types=1);

/**
 * /functional/reclama/_delivery.php
 *
 * Відповідає за:
 * - контекст глядача для показу реклами
 * - перевірку таргетингу
 * - вибір активних кампаній для стрічки і бокових банерів
 * - ротацію round_robin / random
 * - логування показів / кліків
 * - оновлення reclama_campaign_daily_stats
 * - підготовку promoted post у форматі звичайного поста стрічки
 */

if (!function_exists('reclama_mb_lc')) {
    function reclama_mb_lc(string $value): string
    {
        $value = trim($value);
        if ($value === '') return '';

        return function_exists('mb_strtolower')
            ? (string)mb_strtolower($value, 'UTF-8')
            : strtolower($value);
    }
}

if (!function_exists('reclama_norm_city')) {
    function reclama_norm_city(?string $value): string
    {
        $value = trim((string)$value);
        if ($value === '') return '';

        $value = preg_replace('/\s+/u', ' ', $value) ?? $value;
        return reclama_mb_lc($value);
    }
}

if (!function_exists('reclama_age_from_birthdate')) {
    function reclama_age_from_birthdate(?string $birthdate): ?int
    {
        $birthdate = trim((string)$birthdate);
        if ($birthdate === '') return null;

        try {
            $dt  = new DateTimeImmutable($birthdate);
            $now = new DateTimeImmutable(date('Y-m-d'));

            if ($dt > $now) return null;

            $age = (int)$dt->diff($now)->y;
            if ($age < 0 || $age > 120) return null;

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

if (!function_exists('reclama_viewer_session_key')) {
    function reclama_viewer_session_key(?int $userId = null): string
    {
        if (function_exists('ensure_session_started')) {
            ensure_session_started();
        } elseif (session_status() !== PHP_SESSION_ACTIVE) {
            @session_start();
        }

        $userId    = (int)$userId;
        $sid       = (string)session_id();
        $visitorId = trim((string)($_COOKIE['visitor_id'] ?? ''));

        if ($userId > 0) {
            return hash('sha256', 'uid:' . $userId . '|sid:' . $sid);
        }

        if ($visitorId !== '' && preg_match('/^[a-f0-9]{32,64}$/i', $visitorId)) {
            return hash('sha256', 'visitor:' . strtolower($visitorId));
        }

        if ($sid !== '') {
            return hash('sha256', 'sid:' . $sid);
        }

        $ip = '';
        if (function_exists('traffic_client_ip')) {
            try {
                $ip = (string)traffic_client_ip();
            } catch (Throwable $e) {
                $ip = '';
            }
        }

        $ua = trim((string)($_SERVER['HTTP_USER_AGENT'] ?? ''));
        return hash('sha256', 'guest:' . $ip . '|' . $ua);
    }
}

if (!function_exists('reclama_detect_lang_code')) {
    function reclama_detect_lang_code(?array $me = null): string
    {
        $lang = '';

        if (function_exists('lang')) {
            try {
                $lang = (string)lang();
            } catch (Throwable $e) {
                $lang = '';
            }
        }

        if ($lang === '' && is_array($me)) {
            $lang = (string)($me['lang'] ?? '');
        }

        if ($lang === '' && isset($_SESSION['lang'])) {
            $lang = (string)$_SESSION['lang'];
        }

        $lang = trim(strtolower($lang));
        return in_array($lang, ['uk', 'ru', 'en'], true) ? $lang : 'uk';
    }
}

if (!function_exists('reclama_normalize_placement')) {
    function reclama_normalize_placement(string $placement): string
    {
        $placement = trim($placement);

        return in_array($placement, ['feed', 'group_feed', 'profile_feed', 'other'], true)
            ? $placement
            : 'other';
    }
}

if (!function_exists('reclama_viewer_context')) {
    function reclama_viewer_context(PDO $pdo, ?array $me = null, ?string $ua = null): array
    {
        $ua = trim((string)$ua);
        if ($ua === '') {
            $ua = (string)($_SERVER['HTTP_USER_AGENT'] ?? '');
        }

        $viewerUserId = (int)($me['id'] ?? 0);
        $countryCode  = reclama_normalize_country_code((string)($me['country_code'] ?? ''));
        $city         = trim((string)($me['city'] ?? ''));
        $gender       = trim((string)($me['gender'] ?? ''));
        $age          = reclama_age_from_birthdate((string)($me['birthdate'] ?? ($me['birth_date'] ?? '')));
        $langCode     = reclama_detect_lang_code($me);
        $deviceType   = reclama_detect_device_type($ua);
        $isAuth       = ($viewerUserId > 0);

        if (!in_array($gender, ['m', 'f'], true)) {
            $gender = 'any';
        }

        $ip = '';
        if (function_exists('traffic_client_ip')) {
            try {
                $ip = (string)traffic_client_ip();
            } catch (Throwable $e) {
                $ip = '';
            }
        }

        if ($countryCode === null && function_exists('traffic_cf_country')) {
            try {
                $countryCode = reclama_normalize_country_code((string)(traffic_cf_country() ?? ''));
            } catch (Throwable $e) {
                $countryCode = null;
            }
        }

        if ($city === '' && function_exists('traffic_cf_city')) {
            try {
                $city = trim((string)(traffic_cf_city() ?? ''));
            } catch (Throwable $e) {
                $city = '';
            }
        }

        if (($countryCode === null || $city === '') && $ip !== '' && function_exists('traffic_geo_enrich')) {
            try {
                $geo = traffic_geo_enrich($pdo, $ip);
                if (is_array($geo)) {
                    if ($countryCode === null) {
                        $countryCode = reclama_normalize_country_code((string)($geo['country_code'] ?? ''));
                    }
                    if ($city === '') {
                        $city = trim((string)($geo['city'] ?? ''));
                    }
                }
            } catch (Throwable $e) {
                // ignore
            }
        }

        $segmentCodes = $isAuth ? ['users', 'authenticated'] : ['guests'];

        return [
            'viewer_user_id'     => $viewerUserId > 0 ? $viewerUserId : null,
            'viewer_session_key' => reclama_viewer_session_key($viewerUserId > 0 ? $viewerUserId : null),
            'is_authenticated'   => $isAuth,
            'country_code'       => $countryCode,
            'city'               => ($city !== '' ? mb_substr($city, 0, 120) : null),
            'gender'             => $gender,
            'age'                => $age,
            'lang_code'          => $langCode,
            'device_type'        => in_array($deviceType, ['desktop', 'mobile', 'tablet', 'unknown'], true) ? $deviceType : 'unknown',
            'segment_codes'      => $segmentCodes,
            'ip'                 => $ip,
            'ua'                 => $ua,
        ];
    }
}

if (!function_exists('reclama_viewer_is_desktop')) {
    function reclama_viewer_is_desktop(array $viewer): bool
    {
        return trim((string)($viewer['device_type'] ?? 'unknown')) === 'desktop';
    }
}

if (!function_exists('reclama_viewer_is_authenticated')) {
    function reclama_viewer_is_authenticated(array $viewer): bool
    {
        if (array_key_exists('is_authenticated', $viewer)) {
            return (bool)$viewer['is_authenticated'];
        }

        return ((int)($viewer['viewer_user_id'] ?? 0) > 0);
    }
}

if (!function_exists('reclama_viewer_segment_codes')) {
    function reclama_viewer_segment_codes(array $viewer): array
    {
        return reclama_normalize_segment_codes($viewer['segment_codes'] ?? []);
    }
}

if (!function_exists('reclama_targeting_matches_viewer')) {
    function reclama_targeting_matches_viewer(array $campaign, array $viewer): bool
    {
        $targetCountries = reclama_target_country_codes(
            $campaign['country_code'] ?? null,
            $campaign['country_codes_json'] ?? null
        );
        $viewerCountry = reclama_normalize_country_code((string)($viewer['country_code'] ?? ''));

        if ($targetCountries) {
            if ($viewerCountry === null || !in_array($viewerCountry, $targetCountries, true)) {
                return false;
            }
        }

        $excludeCountries = reclama_normalize_country_codes_list(
            $campaign['exclude_countries'] ?? ($campaign['exclude_countries_json'] ?? [])
        );
        if ($viewerCountry !== null && in_array($viewerCountry, $excludeCountries, true)) {
            return false;
        }

        $authScope = trim((string)($campaign['auth_scope'] ?? 'all'));
        if (!in_array($authScope, ['all', 'guests', 'users'], true)) {
            $authScope = 'all';
        }

        $viewerIsAuth = reclama_viewer_is_authenticated($viewer);
        if ($authScope === 'guests' && $viewerIsAuth) {
            return false;
        }
        if ($authScope === 'users' && !$viewerIsAuth) {
            return false;
        }

        $segmentCodes = reclama_normalize_segment_codes(
            $campaign['segment_codes'] ?? ($campaign['segment_codes_json'] ?? [])
        );
        if ($segmentCodes) {
            $viewerSegments = reclama_viewer_segment_codes($viewer);
            if (!$viewerSegments || !array_intersect($segmentCodes, $viewerSegments)) {
                return false;
            }
        }

        $targetCity = reclama_norm_city((string)($campaign['city'] ?? ''));
        $viewerCity = reclama_norm_city((string)($viewer['city'] ?? ''));
        if ($targetCity !== '' && $viewerCity !== $targetCity) {
            return false;
        }

        $targetGender = trim((string)($campaign['gender'] ?? 'any'));
        if (!in_array($targetGender, ['any', 'm', 'f'], true)) {
            $targetGender = 'any';
        }

        $viewerGender = trim((string)($viewer['gender'] ?? 'any'));
        if (!in_array($viewerGender, ['m', 'f'], true)) {
            $viewerGender = 'any';
        }

        if ($targetGender !== 'any' && $viewerGender !== $targetGender) {
            return false;
        }

        $targetLang = trim((string)($campaign['lang_code'] ?? 'any'));
        if (!in_array($targetLang, ['any', 'uk', 'ru', 'en'], true)) {
            $targetLang = 'any';
        }

        $viewerLang = trim((string)($viewer['lang_code'] ?? 'uk'));
        if (!in_array($viewerLang, ['uk', 'ru', 'en'], true)) {
            $viewerLang = 'uk';
        }

        if ($targetLang !== 'any' && $viewerLang !== $targetLang) {
            return false;
        }

        $targetDevice = trim((string)($campaign['device_type'] ?? 'all'));
        if (!in_array($targetDevice, ['all', 'desktop', 'mobile'], true)) {
            $targetDevice = 'all';
        }

        $viewerDevice = trim((string)($viewer['device_type'] ?? 'unknown'));
        if ($viewerDevice === 'tablet') {
            $viewerDevice = 'mobile';
        }
        if (!in_array($viewerDevice, ['desktop', 'mobile', 'unknown'], true)) {
            $viewerDevice = 'unknown';
        }

        if ($targetDevice !== 'all' && $viewerDevice !== $targetDevice) {
            return false;
        }

        $ageFrom = isset($campaign['age_from']) && $campaign['age_from'] !== null && $campaign['age_from'] !== ''
            ? (int)$campaign['age_from']
            : null;
        $ageTo = isset($campaign['age_to']) && $campaign['age_to'] !== null && $campaign['age_to'] !== ''
            ? (int)$campaign['age_to']
            : null;
        $viewerAge = isset($viewer['age']) && $viewer['age'] !== null ? (int)$viewer['age'] : null;

        if (($ageFrom !== null || $ageTo !== null) && $viewerAge === null) {
            return false;
        }
        if ($ageFrom !== null && $viewerAge !== null && $viewerAge < $ageFrom) {
            return false;
        }
        if ($ageTo !== null && $viewerAge !== null && $viewerAge > $ageTo) {
            return false;
        }

        return true;
    }
}

if (!function_exists('reclama_daily_stats_columns')) {
    function reclama_daily_stats_columns(PDO $pdo): array
    {
        static $cache = null;
        if (is_array($cache)) return $cache;

        $cache = [
            'impressions' => null,
            'clicks'      => null,
            'unique'      => null,
        ];

        if (!reclama_table_exists($pdo, 'reclama_campaign_daily_stats')) {
            return $cache;
        }

        $cache['impressions'] = reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'impressions_count')
            ? 'impressions_count'
            : (reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'impressions') ? 'impressions' : null);

        $cache['clicks'] = reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'clicks_count')
            ? 'clicks_count'
            : (reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'clicks') ? 'clicks' : null);

        $cache['unique'] = reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'unique_viewers_count')
            ? 'unique_viewers_count'
            : (reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'unique_viewers') ? 'unique_viewers' : null);

        return $cache;
    }
}

if (!function_exists('reclama_daily_stats_inc')) {
    function reclama_daily_stats_inc(PDO $pdo, int $campaignId, string $statDate, int $impDelta = 0, int $clkDelta = 0, int $uniqDelta = 0): void
    {
        if ($campaignId <= 0 || !reclama_table_exists($pdo, 'reclama_campaign_daily_stats')) {
            return;
        }

        $cols = reclama_daily_stats_columns($pdo);
        if ($cols['impressions'] === null && $cols['clicks'] === null && $cols['unique'] === null) {
            return;
        }

        $insertCols = ['campaign_id', 'stat_date'];
        $valuesCols = ['?', '?'];
        $bind       = [$campaignId, $statDate];
        $updates    = ['updated_at = CURRENT_TIMESTAMP'];

        if ($cols['impressions'] !== null) {
            $insertCols[] = $cols['impressions'];
            $valuesCols[] = '?';
            $bind[] = max(0, $impDelta);
            $updates[] = '`' . $cols['impressions'] . '` = `' . $cols['impressions'] . '` + VALUES(`' . $cols['impressions'] . '`)';
        }

        if ($cols['clicks'] !== null) {
            $insertCols[] = $cols['clicks'];
            $valuesCols[] = '?';
            $bind[] = max(0, $clkDelta);
            $updates[] = '`' . $cols['clicks'] . '` = `' . $cols['clicks'] . '` + VALUES(`' . $cols['clicks'] . '`)';
        }

        if ($cols['unique'] !== null) {
            $insertCols[] = $cols['unique'];
            $valuesCols[] = '?';
            $bind[] = max(0, $uniqDelta);
            $updates[] = '`' . $cols['unique'] . '` = `' . $cols['unique'] . '` + VALUES(`' . $cols['unique'] . '`)';
        }

        if (reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'created_at')) {
            $insertCols[] = 'created_at';
            $valuesCols[] = 'CURRENT_TIMESTAMP';
        }
        if (reclama_column_exists($pdo, 'reclama_campaign_daily_stats', 'updated_at')) {
            $insertCols[] = 'updated_at';
            $valuesCols[] = 'CURRENT_TIMESTAMP';
        }

        $sql = '
            INSERT INTO reclama_campaign_daily_stats (' . implode(', ', array_map(static fn($c) => '`' . $c . '`', $insertCols)) . ')
            VALUES (' . implode(', ', $valuesCols) . ')
            ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);

        try {
            $st = $pdo->prepare($sql);
            $st->execute($bind);
        } catch (Throwable $e) {
            // do not break page
        }
    }
}

if (!function_exists('reclama_has_impression_today')) {
    function reclama_has_impression_today(PDO $pdo, int $campaignId, array $viewer): bool
    {
        if ($campaignId <= 0 || !reclama_table_exists($pdo, 'reclama_campaign_impressions')) {
            return false;
        }

        $viewerUserId = (int)($viewer['viewer_user_id'] ?? 0);
        $sessionKey   = trim((string)($viewer['viewer_session_key'] ?? ''));

        if ($viewerUserId <= 0 && $sessionKey === '') {
            return false;
        }

        try {
            if ($viewerUserId > 0) {
                $st = $pdo->prepare('
                    SELECT COUNT(*)
                    FROM reclama_campaign_impressions
                    WHERE campaign_id = ?
                      AND viewer_user_id = ?
                      AND DATE(shown_at) = CURRENT_DATE()
                ');
                $st->execute([$campaignId, $viewerUserId]);
                return ((int)$st->fetchColumn() > 0);
            }

            $st = $pdo->prepare('
                SELECT COUNT(*)
                FROM reclama_campaign_impressions
                WHERE campaign_id = ?
                  AND viewer_session_key = ?
                  AND DATE(shown_at) = CURRENT_DATE()
            ');
            $st->execute([$campaignId, $sessionKey]);
            return ((int)$st->fetchColumn() > 0);
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('reclama_impressions_today_for_viewer')) {
    function reclama_impressions_today_for_viewer(PDO $pdo, int $campaignId, array $viewer): int
    {
        if ($campaignId <= 0 || !reclama_table_exists($pdo, 'reclama_campaign_impressions')) {
            return 0;
        }

        $viewerUserId = (int)($viewer['viewer_user_id'] ?? 0);
        $sessionKey   = trim((string)($viewer['viewer_session_key'] ?? ''));

        if ($viewerUserId <= 0 && $sessionKey === '') {
            return 0;
        }

        try {
            if ($viewerUserId > 0) {
                $st = $pdo->prepare('
                    SELECT COUNT(*)
                    FROM reclama_campaign_impressions
                    WHERE campaign_id = ?
                      AND viewer_user_id = ?
                      AND DATE(shown_at) = CURRENT_DATE()
                ');
                $st->execute([$campaignId, $viewerUserId]);
                return (int)$st->fetchColumn();
            }

            $st = $pdo->prepare('
                SELECT COUNT(*)
                FROM reclama_campaign_impressions
                WHERE campaign_id = ?
                  AND viewer_session_key = ?
                  AND DATE(shown_at) = CURRENT_DATE()
            ');
            $st->execute([$campaignId, $sessionKey]);
            return (int)$st->fetchColumn();
        } catch (Throwable $e) {
            return 0;
        }
    }
}

if (!function_exists('reclama_log_impression')) {
    function reclama_log_impression(PDO $pdo, int $campaignId, array $viewer, string $placement = 'feed'): array
    {
        if ($campaignId <= 0) {
            return ['ok' => false, 'reason' => 'bad_campaign'];
        }

        $placement    = reclama_normalize_placement($placement);
        $viewerUserId = (int)($viewer['viewer_user_id'] ?? 0);
        $sessionKey   = trim((string)($viewer['viewer_session_key'] ?? ''));
        $countryCode  = reclama_normalize_country_code((string)($viewer['country_code'] ?? ''));
        $city         = trim((string)($viewer['city'] ?? ''));
        $deviceType   = trim((string)($viewer['device_type'] ?? 'unknown'));

        if (!in_array($deviceType, ['desktop', 'mobile', 'tablet', 'unknown'], true)) {
            $deviceType = 'unknown';
        }

        $uniqueToday = !reclama_has_impression_today($pdo, $campaignId, $viewer);

        if (reclama_table_exists($pdo, 'reclama_campaign_impressions')) {
            try {
                $st = $pdo->prepare('
                    INSERT INTO reclama_campaign_impressions
                        (campaign_id, viewer_user_id, viewer_session_key, country_code, city, device_type, placement, shown_at)
                    VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
                ');
                $st->execute([
                    $campaignId,
                    $viewerUserId > 0 ? $viewerUserId : null,
                    $sessionKey !== '' ? $sessionKey : null,
                    $countryCode,
                    $city !== '' ? mb_substr($city, 0, 120) : null,
                    $deviceType,
                    $placement,
                ]);
            } catch (Throwable $e) {
                // ignore
            }
        }

        reclama_daily_stats_inc($pdo, $campaignId, date('Y-m-d'), 1, 0, $uniqueToday ? 1 : 0);

        return ['ok' => true, 'unique_today' => $uniqueToday];
    }
}

if (!function_exists('reclama_log_click')) {
    function reclama_log_click(PDO $pdo, int $campaignId, array $viewer, string $placement = 'feed', ?string $clickedUrl = null): array
    {
        if ($campaignId <= 0) {
            return ['ok' => false, 'reason' => 'bad_campaign'];
        }

        $placement    = reclama_normalize_placement($placement);
        $viewerUserId = (int)($viewer['viewer_user_id'] ?? 0);
        $sessionKey   = trim((string)($viewer['viewer_session_key'] ?? ''));
        $countryCode  = reclama_normalize_country_code((string)($viewer['country_code'] ?? ''));
        $city         = trim((string)($viewer['city'] ?? ''));
        $deviceType   = trim((string)($viewer['device_type'] ?? 'unknown'));

        if (!in_array($deviceType, ['desktop', 'mobile', 'tablet', 'unknown'], true)) {
            $deviceType = 'unknown';
        }

        if (reclama_table_exists($pdo, 'reclama_campaign_clicks')) {
            try {
                $st = $pdo->prepare('
                    INSERT INTO reclama_campaign_clicks
                        (campaign_id, viewer_user_id, viewer_session_key, country_code, city, device_type, placement, clicked_url, clicked_at)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
                ');
                $st->execute([
                    $campaignId,
                    $viewerUserId > 0 ? $viewerUserId : null,
                    $sessionKey !== '' ? $sessionKey : null,
                    $countryCode,
                    $city !== '' ? mb_substr($city, 0, 120) : null,
                    $deviceType,
                    $placement,
                    $clickedUrl !== null && $clickedUrl !== '' ? mb_substr($clickedUrl, 0, 2048) : null,
                ]);
            } catch (Throwable $e) {
                // ignore
            }
        }

        reclama_daily_stats_inc($pdo, $campaignId, date('Y-m-d'), 0, 1, 0);

        return ['ok' => true];
    }
}

if (!function_exists('reclama_pick_rotated_campaign')) {
    function reclama_pick_rotated_campaign(array $candidates, string $slotKey, string $rotationMode = 'round_robin'): ?array
    {
        if (!$candidates) return null;

        if (function_exists('ensure_session_started')) {
            ensure_session_started();
        } elseif (session_status() !== PHP_SESSION_ACTIVE) {
            @session_start();
        }

        $slotKey      = preg_replace('~[^a-zA-Z0-9:_\-]+~', '_', trim($slotKey)) ?: 'feed';
        $rotationMode = in_array($rotationMode, ['round_robin', 'random'], true) ? $rotationMode : 'round_robin';

        if (!isset($_SESSION['_reclama_rr']) || !is_array($_SESSION['_reclama_rr'])) {
            $_SESSION['_reclama_rr'] = [];
        }
        if (!isset($_SESSION['_reclama_last']) || !is_array($_SESSION['_reclama_last'])) {
            $_SESSION['_reclama_last'] = [];
        }

        $lastShownId = (int)($_SESSION['_reclama_last'][$slotKey] ?? 0);
        $selected    = null;

        if ($rotationMode === 'random') {
            shuffle($candidates);

            if (count($candidates) > 1 && (int)($candidates[0]['id'] ?? 0) === $lastShownId) {
                for ($i = 1, $n = count($candidates); $i < $n; $i++) {
                    if ((int)($candidates[$i]['id'] ?? 0) !== $lastShownId) {
                        $tmp = $candidates[0];
                        $candidates[0] = $candidates[$i];
                        $candidates[$i] = $tmp;
                        break;
                    }
                }
            }

            $selected = $candidates[0] ?? null;
        } else {
            usort($candidates, static function (array $a, array $b): int {
                return ((int)($a['id'] ?? 0) <=> (int)($b['id'] ?? 0));
            });

            $lastCursor = (int)($_SESSION['_reclama_rr'][$slotKey] ?? 0);
            $count      = count($candidates);
            $startIndex = 0;

            if ($lastCursor > 0) {
                foreach ($candidates as $idx => $item) {
                    if ((int)($item['id'] ?? 0) === $lastCursor) {
                        $startIndex = ($idx + 1) % $count;
                        break;
                    }
                }
            }

            for ($step = 0; $step < $count; $step++) {
                $idx    = ($startIndex + $step) % $count;
                $item   = $candidates[$idx];
                $itemId = (int)($item['id'] ?? 0);

                if ($count > 1 && $itemId === $lastShownId) {
                    continue;
                }

                $selected = $item;
                break;
            }

            if ($selected === null) {
                $selected = $candidates[0] ?? null;
            }
        }

        if ($selected !== null) {
            $selectedId = (int)($selected['id'] ?? 0);
            $_SESSION['_reclama_rr'][$slotKey]   = $selectedId;
            $_SESSION['_reclama_last'][$slotKey] = $selectedId;
        }

        return $selected;
    }
}

if (!function_exists('reclama_fetch_candidate_campaigns')) {
    function reclama_fetch_candidate_campaigns(
        PDO $pdo,
        string $campaignType,
        array $viewer,
        string $placement = 'feed',
        array $excludeCampaignIds = [],
        array $excludePostIds = []
    ): array {
        if (!reclama_is_enabled($pdo) || !reclama_table_exists($pdo, 'reclama_campaigns')) {
            return [];
        }

        $campaignType = ($campaignType === 'banner') ? 'banner' : 'post';
        $placement    = reclama_normalize_placement($placement);
        $today        = date('Y-m-d');

        if ($campaignType === 'post' && !reclama_allow_post_campaigns($pdo)) {
            return [];
        }
        if ($campaignType === 'banner' && !reclama_allow_banner_campaigns($pdo)) {
            return [];
        }
        if ($campaignType === 'banner' && !reclama_viewer_is_desktop($viewer)) {
            return [];
        }

        $hasTargeting        = reclama_table_exists($pdo, 'reclama_campaign_targeting');
        $hasPosts            = reclama_table_exists($pdo, 'posts');
        $hasCountryCodesJson = $hasTargeting && reclama_column_exists($pdo, 'reclama_campaign_targeting', 'country_codes_json');

        $sql = '
            SELECT
                c.*,' .
                ($hasTargeting ? '
                t.country_code,
                ' . ($hasCountryCodesJson ? 't.country_codes_json,' : 'NULL AS country_codes_json,') . '
                t.exclude_countries_json,
                t.city,
                t.gender,
                t.age_from,
                t.age_to,
                t.lang_code,
                t.device_type,
                t.auth_scope,
                t.segment_codes_json' : '
                NULL AS country_code,
                NULL AS country_codes_json,
                NULL AS exclude_countries_json,
                NULL AS city,
                "any" AS gender,
                NULL AS age_from,
                NULL AS age_to,
                "any" AS lang_code,
                "all" AS device_type,
                "all" AS auth_scope,
                NULL AS segment_codes_json') . '
            FROM reclama_campaigns c
            ' . ($hasTargeting ? 'LEFT JOIN reclama_campaign_targeting t ON t.campaign_id = c.id' : '') . '
            ' . ($campaignType === 'post' && $hasPosts ? 'INNER JOIN posts sp ON sp.id = c.source_post_id' : '') . '
            WHERE c.campaign_type = ?
              AND c.status = "active"
              AND c.moderation_status = "approved"
              AND c.payment_status IN ("paid","reserved")
              AND c.start_date <= ?
              AND c.end_date >= ?';

        $bind = [$campaignType, $today, $today];

        if ($campaignType === 'post') {
            $sql .= ' AND c.source_post_id IS NOT NULL';
        } else {
            $sql .= ' AND c.desktop_banner_path IS NOT NULL AND c.desktop_banner_path <> ""';
            $sql .= ' AND c.target_url IS NOT NULL AND c.target_url <> ""';
        }

        $sql .= ' ORDER BY c.id ASC';

        try {
            $st = $pdo->prepare($sql);
            $st->execute($bind);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }

        $excludeCampaignIds = array_map('intval', $excludeCampaignIds);
        $excludePostIds     = array_map('intval', $excludePostIds);
        $cap                = reclama_frequency_cap_per_user_day($pdo);

        $out = [];
        foreach ($rows as $row) {
            $campaignId   = (int)($row['id'] ?? 0);
            $sourcePostId = (int)($row['source_post_id'] ?? 0);

            if ($campaignId <= 0) continue;
            if (in_array($campaignId, $excludeCampaignIds, true)) continue;
            if ($campaignType === 'post' && $sourcePostId > 0 && in_array($sourcePostId, $excludePostIds, true)) continue;
            if (!reclama_is_active_now($row, $today)) continue;
            if (reclama_missing_required_assets($pdo, $row)) continue;

            $row['country_codes']     = reclama_target_country_codes($row['country_code'] ?? null, $row['country_codes_json'] ?? null);
            $row['exclude_countries'] = reclama_normalize_country_codes_list($row['exclude_countries_json'] ?? null);
            $row['segment_codes']     = reclama_normalize_segment_codes($row['segment_codes_json'] ?? null);
            $row['auth_scope']        = in_array((string)($row['auth_scope'] ?? 'all'), ['all', 'guests', 'users'], true)
                ? (string)$row['auth_scope']
                : 'all';

            if (!reclama_targeting_matches_viewer($row, $viewer)) continue;

            if ($cap > 0) {
                $shownToday = reclama_impressions_today_for_viewer($pdo, $campaignId, $viewer);
                if ($shownToday >= $cap) {
                    continue;
                }
            }

            $out[] = $row;
        }

        return $out;
    }
}

if (!function_exists('reclama_select_feed_campaign')) {
    function reclama_select_feed_campaign(PDO $pdo, array $viewer, array $excludePostIds = [], string $placement = 'feed'): ?array
    {
        $candidates = reclama_fetch_candidate_campaigns($pdo, 'post', $viewer, $placement, [], $excludePostIds);
        if (!$candidates) return null;

        return reclama_pick_rotated_campaign(
            $candidates,
            'reclama:' . reclama_normalize_placement($placement) . ':post',
            reclama_rotation_mode($pdo)
        );
    }
}

if (!function_exists('reclama_select_sidebar_campaigns')) {
    function reclama_select_sidebar_campaigns(PDO $pdo, array $viewer, int $limit = 2, array $excludeCampaignIds = []): array
    {
        if (!reclama_viewer_is_desktop($viewer)) {
            return [];
        }

        $limit = max(1, min(2, $limit));
        $pool  = reclama_fetch_candidate_campaigns($pdo, 'banner', $viewer, 'other', $excludeCampaignIds, []);
        if (!$pool) return [];

        $selected = [];
        for ($i = 0; $i < $limit; $i++) {
            $pick = reclama_pick_rotated_campaign(
                $pool,
                'reclama:other:banner:' . $i,
                reclama_rotation_mode($pdo)
            );
            if (!$pick) break;

            $selected[] = $pick;
            $pickId = (int)($pick['id'] ?? 0);

            $pool = array_values(array_filter($pool, static function (array $item) use ($pickId): bool {
                return (int)($item['id'] ?? 0) !== $pickId;
            }));

            if (!$pool) break;
        }

        return $selected;
    }
}

if (!function_exists('reclama_build_click_url')) {
    function reclama_build_click_url(int $campaignId, string $targetUrl, string $placement = 'feed'): string
    {
        $placement = reclama_normalize_placement($placement);

        return '/functional/reclama/click.php?c=' . $campaignId
            . '&p=' . rawurlencode($placement)
            . '&to=' . rawurlencode($targetUrl);
    }
}

if (!function_exists('reclama_load_feed_post_row')) {
    function reclama_load_feed_post_row(PDO $pdo, int $postId, ?int $viewerUserId = null): ?array
    {
        if ($postId <= 0 || !reclama_table_exists($pdo, 'posts') || !reclama_table_exists($pdo, 'users')) {
            return null;
        }

        $viewerUserId      = max(0, (int)$viewerUserId);
        $usersHasFullName  = reclama_column_exists($pdo, 'users', 'full_name');
        $postsHasGroupId   = reclama_column_exists($pdo, 'posts', 'group_id');
        $postsHasRepost    = reclama_column_exists($pdo, 'posts', 'repost_post_id');

        $hasGroupsTable    = $postsHasGroupId && reclama_table_exists($pdo, 'groups');
        $groupHasName      = $hasGroupsTable && reclama_column_exists($pdo, 'groups', 'name');
        $groupHasSlug      = $hasGroupsTable && reclama_column_exists($pdo, 'groups', 'slug');
        $groupHasAva       = $hasGroupsTable && reclama_column_exists($pdo, 'groups', 'avatar_path');
        $groupHasOwner     = $hasGroupsTable && reclama_column_exists($pdo, 'groups', 'owner_user_id');

        $hasGroupMembers   = $postsHasGroupId && $viewerUserId > 0 && reclama_table_exists($pdo, 'group_members');
        $gmHasRole         = $hasGroupMembers && reclama_column_exists($pdo, 'group_members', 'role');
        $gmHasStatus       = $hasGroupMembers && reclama_column_exists($pdo, 'group_members', 'status');

        $uFullNameSel      = $usersHasFullName ? 'u.full_name' : 'NULL';
        $ouFullNameSel     = $usersHasFullName ? 'ou.full_name' : 'NULL';

        $groupSelect = '
                NULL AS group_pk,
                NULL AS group_name,
                NULL AS group_slug,
                NULL AS group_avatar_path,
                0 AS group_owner_user_id,
                NULL AS my_group_role,
                NULL AS my_group_status';

        if ($postsHasGroupId && $hasGroupsTable) {
            $groupSelect = '
                g.id AS group_pk,
                ' . ($groupHasName ? 'g.name' : 'NULL') . ' AS group_name,
                ' . ($groupHasSlug ? 'g.slug' : 'NULL') . ' AS group_slug,
                ' . ($groupHasAva ? 'g.avatar_path' : 'NULL') . ' AS group_avatar_path,
                ' . ($groupHasOwner ? 'g.owner_user_id' : '0') . ' AS group_owner_user_id,
                ' . ($hasGroupMembers && $gmHasRole ? 'gm.role' : 'NULL') . ' AS my_group_role,
                ' . ($hasGroupMembers && $gmHasStatus ? 'gm.status' : 'NULL') . ' AS my_group_status';
        }

        $origSelect = '
                NULL AS orig_id,
                NULL AS orig_user_id,
                NULL AS orig_group_id,
                NULL AS orig_body,
                NULL AS orig_created_at,
                NULL AS orig_username,
                NULL AS orig_first_name,
                NULL AS orig_last_name,
                NULL AS orig_avatar_path,
                NULL AS orig_full_name,
                NULL AS orig_group_pk,
                NULL AS orig_group_name,
                NULL AS orig_group_slug,
                NULL AS orig_group_avatar_path,
                0 AS orig_group_owner_user_id';

        if ($postsHasRepost) {
            $origSelect = '
                op.id AS orig_id,
                op.user_id AS orig_user_id,
                ' . ($postsHasGroupId ? 'op.group_id' : 'NULL') . ' AS orig_group_id,
                op.body AS orig_body,
                op.created_at AS orig_created_at,
                ou.username AS orig_username,
                ou.first_name AS orig_first_name,
                ou.last_name AS orig_last_name,
                ou.avatar_path AS orig_avatar_path,
                ' . $ouFullNameSel . ' AS orig_full_name,
                ' . (($postsHasGroupId && $hasGroupsTable) ? 'og.id' : 'NULL') . ' AS orig_group_pk,
                ' . (($postsHasGroupId && $hasGroupsTable && $groupHasName) ? 'og.name' : 'NULL') . ' AS orig_group_name,
                ' . (($postsHasGroupId && $hasGroupsTable && $groupHasSlug) ? 'og.slug' : 'NULL') . ' AS orig_group_slug,
                ' . (($postsHasGroupId && $hasGroupsTable && $groupHasAva) ? 'og.avatar_path' : 'NULL') . ' AS orig_group_avatar_path,
                ' . (($postsHasGroupId && $hasGroupsTable && $groupHasOwner) ? 'og.owner_user_id' : '0') . ' AS orig_group_owner_user_id';
        }

        $sql = '
            SELECT
                p.id,
                p.user_id,
                ' . ($postsHasGroupId ? 'p.group_id' : 'NULL') . ' AS group_id,
                ' . ($postsHasRepost ? 'p.repost_post_id' : 'NULL') . ' AS repost_post_id,
                p.body,
                p.created_at,
                u.username,
                u.first_name,
                u.last_name,
                u.avatar_path,
                ' . $uFullNameSel . ' AS full_name,
                ' . $groupSelect . ',
                ' . $origSelect . '
            FROM posts p
            INNER JOIN users u ON u.id = p.user_id
            ' . (($postsHasGroupId && $hasGroupsTable) ? 'LEFT JOIN `groups` g ON g.id = p.group_id' : '') . '
            ' . (($postsHasGroupId && $hasGroupsTable && $hasGroupMembers) ? 'LEFT JOIN group_members gm ON gm.group_id = p.group_id AND gm.user_id = ?' : '') . '
            ' . ($postsHasRepost ? 'LEFT JOIN posts op ON op.id = p.repost_post_id LEFT JOIN users ou ON ou.id = op.user_id' : '') . '
            ' . (($postsHasRepost && $postsHasGroupId && $hasGroupsTable) ? 'LEFT JOIN `groups` og ON og.id = op.group_id' : '') . '
            WHERE p.id = ?
            LIMIT 1
        ';

        try {
            $bind = [];
            if ($postsHasGroupId && $hasGroupsTable && $hasGroupMembers) {
                $bind[] = $viewerUserId;
            }
            $bind[] = $postId;

            $st = $pdo->prepare($sql);
            $st->execute($bind);

            $row = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('reclama_promoted_post_row')) {
    function reclama_promoted_post_row(PDO $pdo, array $campaign, ?int $viewerUserId = null): ?array
    {
        $campaignId   = (int)($campaign['id'] ?? 0);
        $sourcePostId = (int)($campaign['source_post_id'] ?? 0);

        if ($campaignId <= 0 || $sourcePostId <= 0) {
            return null;
        }

        $row = reclama_load_feed_post_row($pdo, $sourcePostId, $viewerUserId);
        if (!$row) return null;

        $targetUrl = trim((string)($campaign['target_url'] ?? ''));

        $row['is_promoted']          = 1;
        $row['promoted_campaign_id'] = $campaignId;
        $row['promoted_target_url']  = $targetUrl;
        $row['promoted_click_url']   = $targetUrl !== ''
            ? reclama_build_click_url($campaignId, $targetUrl, 'feed')
            : '';
        $row['promoted_title']       = trim((string)($campaign['title'] ?? ''));

        return $row;
    }
}

if (!function_exists('reclama_insert_promoted_into_feed')) {
    function reclama_insert_promoted_into_feed(array $posts, ?array $promotedRow, int $interval): array
    {
        if (!$promotedRow) return $posts;

        $interval = max(1, $interval);

        if (!$posts) {
            return [$promotedRow];
        }

        $insertAt    = count($posts);
        $organicSeen = 0;

        foreach ($posts as $idx => $post) {
            $organicSeen++;
            if ($organicSeen >= $interval) {
                $insertAt = $idx + 1;
                break;
            }
        }

        array_splice($posts, $insertAt, 0, [$promotedRow]);
        return $posts;
    }
}