<?php

declare(strict_types=1);



class Ads
{
    private static ?bool $enabled = null;
    private static array $zoneCache = [];    
    private static array $spotCache = [];    
    private static bool $cssDone = false;
    private static bool $pausePicked = false;
    private static ?array $pausePick = null;
    private static array $counters = [];     

     
    private const FORMATS = [
        'header_banner'    => 'leaderboard', 'footer_banner'    => 'leaderboard',
        'before_player'    => 'leaderboard', 'after_player'     => 'leaderboard',
        'before_comments'  => 'leaderboard', 'between_related'  => 'native',
        'listing_top'      => 'leaderboard', 'listing_bottom'   => 'leaderboard',
        'category_top'     => 'leaderboard', 'category_bottom'  => 'leaderboard',
        'performer_top'    => 'leaderboard', 'performer_bottom' => 'leaderboard',
        'homepage_hero'    => 'billboard',
        'homepage_hero_left' => 'square', 'homepage_hero_right' => 'square',
        'homepage_bottom'  => 'leaderboard',
        'sidebar'          => 'rectangle',
        'mobile_sticky'    => 'sticky',
        'player_overlay'   => 'overlay',
        'player_pause'     => 'pause',
        'listing_between'  => 'native', 'category_between'  => 'native',
        'performer_between'=> 'native', 'homepage_between' => 'native',
        'search_between'   => 'native', 'tag_between'      => 'native',
        'album_top'        => 'leaderboard', 'album_bottom'         => 'leaderboard',
        'album_before_comments' => 'leaderboard', 'album_between_related' => 'native',
        'album_listing_top' => 'leaderboard', 'album_listing_bottom' => 'leaderboard',
        'album_listing_between' => 'native',
    ];

     
    private const ZONE_INFO = [
        'header_banner'      => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'footer_banner'      => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'before_player'      => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'after_player'       => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'before_comments'    => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'between_related'    => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'listing_top'        => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'listing_bottom'     => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'category_top'       => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'category_bottom'    => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'performer_top'      => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'performer_bottom'   => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'homepage_hero'      => ['billboard', '970 × 250', 'Hero billboard'],
        'homepage_hero_left' => ['square', '250 × 250', 'Square (left of hero)'],
        'homepage_hero_right'=> ['square', '250 × 250', 'Square (right of hero)'],
        'sidebar'            => ['rectangle', '300 × 250', 'Medium rectangle'],
        'listing_between'    => ['native', '600 × 340', 'In-feed card (16:9)'],
        'category_between'   => ['native', '600 × 340', 'In-feed card (16:9)'],
        'performer_between'  => ['native', '600 × 340', 'In-feed card (16:9)'],
        'homepage_between'   => ['native', '600 × 340', 'In-feed card (16:9)'],
        'mobile_sticky'      => ['sticky', '320 × 50', 'Mobile banner'],
        'in_player'          => ['vast', '—', 'Video ad (VAST)'],
        'player_overlay'     => ['overlay', '728 × 90', 'Overlay banner'],
        'player_pause'       => ['pause', '600 × 450', 'Pause card (4:3)'],
        'popup'              => ['code', '—', 'Ad-network code'],
        'player_popunder'    => ['code', '—', 'Popunder (player-triggered)'],
        'interstitial'       => ['image', '900 × 600', 'Full-screen interstitial'],
        'push'               => ['image', '120 × 120', 'In-page push (icon + headline)'],
        'album_top'             => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'album_bottom'          => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'album_before_comments' => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'album_between_related' => ['native', '600 × 340', 'In-feed card (16:9)'],
        'album_listing_top'     => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'album_listing_bottom'  => ['leaderboard', '728 × 90', 'Leaderboard banner'],
        'album_listing_between' => ['native', '600 × 340', 'In-feed card (16:9)'],
    ];

    public static function registerRoutes(): void
    {
        Router::get('/go/{id}', [self::class, 'clickRedirect']);
        Router::post('/ev/{id}', [self::class, 'trackPopFire']);  
        Router::post('/iv/{id}', [self::class, 'trackView']);     
        Router::get('/ads.txt', [self::class, 'adsTxt']);
    }

     
    public static function adsTxt(): void
    {
        header('Content-Type: text/plain; charset=utf-8');
        header('Cache-Control: public, max-age=3600');
        echo trim((string) Setting::get('ads_txt', ''));
        exit;
    }

     
    public static function trackView(): void
    {
        $id = (int) Router::param('id');
        if ($id > 0 && (!class_exists('RateLimiter') || RateLimiter::throttle('flux_iv_' . $id, 240, 3000))) {
            self::trackImpression($id);
            self::logEvent($id, 'impression');
        }
        http_response_code(204);
        exit;
    }

     
    private static function logEvent(int $spotId, string $type): void
    {
        if ($spotId <= 0 || !in_array($type, ['impression', 'click'], true)) {
            return;
        }
        try {
            $zid = (int) (Database::fetchOne('SELECT zone_id FROM ad_spots WHERE id = ?', [$spotId])['zone_id'] ?? 0);
            if ($zid <= 0) {
                return;
            }
            Database::query(
                "INSERT INTO ad_events (spot_id, zone_id, event_type, event_date, cnt) VALUES (?, ?, ?, CURDATE(), 1)
                 ON DUPLICATE KEY UPDATE cnt = cnt + 1",
                [$spotId, $zid, $type]
            );
        } catch (\Throwable) {
        }
    }

    public static function enabled(): bool
    {
        if (self::$enabled === null) {
            self::$enabled = Setting::get('ads_enabled', '1') === '1';
        }
        return self::$enabled;
    }

    

    public static function zoneInfo(string $slug): array
    {
        $i = self::ZONE_INFO[$slug] ?? ['leaderboard', '728 × 90', 'Banner'];
        return ['format' => $i[0], 'size' => $i[1], 'label' => $i[2], 'image' => !in_array($i[0], ['vast', 'code'], true)];
    }

     
    public static function isHomepageLanding(): bool
    {
        $path = rtrim((string) (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/'), '/');
         
         
         
         
        if (class_exists('Router')) {
            foreach ([Router::langPrefix(), Router::typePrefix()] as $pfx) {
                $pfx = rtrim((string) $pfx, '/');
                if ($pfx !== '' && ($path === $pfx || str_starts_with($path, $pfx . '/'))) {
                    $path = rtrim(substr($path, strlen($pfx)), '/');
                }
            }
        }
        if ($path !== '') {
            return false;
        }
        return (int) ($_GET['page'] ?? 1) <= 1;
    }

     
    public static function isWatchPage(): bool
    {
        $path = rtrim((string) (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/'), '/');
        if (class_exists('Router')) {
            foreach ([Router::langPrefix(), Router::typePrefix()] as $pfx) {
                $pfx = rtrim((string) $pfx, '/');
                if ($pfx !== '' && ($path === $pfx || str_starts_with($path, $pfx . '/'))) {
                    $path = rtrim(substr($path, strlen($pfx)), '/');
                }
            }
        }
        return str_starts_with($path, '/video/');
    }

     
    public static function zone(string $slug): void
    {
        if (!self::enabled()) {
            return;
        }
        $spot = $slug === 'player_pause' ? self::pausePick() : self::pickSpot($slug);
        if (!$spot) {
            return;
        }
        if ($slug === 'player_pause' && ($spot['ad_type'] ?? '') === 'vast') {
            return;
        }
        self::ensureCss();
        echo self::renderSpot($spot, self::FORMATS[$slug] ?? 'leaderboard', $slug);
         
    }

    

    public static function heroBlock(): void
    {
        if (!self::enabled()) {
            return;
        }
        $center = self::pickSpot('homepage_hero');
        if (!$center) {
            return;
        }
        self::ensureCss();
        $left  = self::pickSpot('homepage_hero_left');
        $right = self::pickSpot('homepage_hero_right');

        echo '<div class="flux-hero" data-flux="homepage_hero">' . self::closeBtn() . '<span class="flux__tag">Sponsored</span><div class="flux-hero__row">';
        if ($left) {
            echo '<div class="flux-hero__side" data-flux-id="' . (int) $left['id'] . '">' . self::creativeInner($left) . '</div>';
        }
        echo '<div class="flux-hero__center" data-flux-id="' . (int) $center['id'] . '">' . self::creativeInner($center) . '</div>';
        if ($right) {
            echo '<div class="flux-hero__side" data-flux-id="' . (int) $right['id'] . '">' . self::creativeInner($right) . '</div>';
        }
        echo '</div></div>';
    }

    

    public static function betweenGrid(string $slug, int $every = 12): void
    {
        if (!self::enabled()) {
            return;
        }
        $n = (self::$counters[$slug] ?? 0) + 1;
        self::$counters[$slug] = $n;
        if ($n % max(1, $every) !== 0) {
            return;
        }
        self::zone($slug);
    }

    

    public static function playerVastJson(): string
    {
        if (!self::enabled()) {
            return '';
        }
        $ads = [];
        foreach (self::getZoneSpots('in_player') as $s) {
            $vurl = trim((string) ($s['vast_url'] ?? ''));
            if (($s['ad_type'] ?? '') !== 'vast' || !preg_match('#^https?://#i', $vurl)) {
                continue;  
            }
            $roll = match ($s['player_position'] ?? 'preroll') {
                'midroll'  => 'midRoll',
                'postroll' => 'postRoll',
                default    => 'preRoll',
            };
            $ad = ['roll' => $roll, 'vastTag' => $vurl];
            if ($roll === 'midRoll') {
                $ad['timer'] = max(1, min(99, (int) ($s['midroll_at'] ?? 50))) . '%';
            }
            $ads[] = $ad;
            self::trackImpression((int) $s['id']);
            self::logEvent((int) $s['id'], 'impression');
        }
        $pause = self::pauseVastSpot();
        if ($pause !== null) {
            $ads[] = ['roll' => 'onPauseRoll', 'vastTag' => trim((string) $pause['vast_url']), 'vAlign' => 'middle'];
        }
        return empty($ads) ? '' : json_encode($ads, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT);
    }

    public static function pauseVastSpotId(): int
    {
        $spot = self::pauseVastSpot();
        return $spot !== null ? (int) $spot['id'] : 0;
    }

    private static function pauseVastSpot(): ?array
    {
        if (!self::enabled()) {
            return null;
        }
        $spot = self::pausePick();
        if (!$spot || ($spot['ad_type'] ?? '') !== 'vast') {
            return null;
        }
        return preg_match('#^https?://#i', trim((string) ($spot['vast_url'] ?? ''))) ? $spot : null;
    }

    private static function pausePick(): ?array
    {
        if (!self::$pausePicked) {
            self::$pausePicked = true;
            self::$pausePick = self::pickSpot('player_pause');
        }
        return self::$pausePick;
    }

    

    public static function popunder(): void
    {
        $preview = isset($_GET['flux_preview']) && class_exists('Auth') && Auth::isAdmin();
        if (!$preview) {
            if (!self::enabled() || Setting::get('ads_popunder_enabled', '0') !== '1') {
                return;  
            }
        }

        $cfg = [];
         
        foreach (self::getZoneSpots('popup') as $s) {
            $e = self::popEntry($s, 'click');
            if ($e) { $cfg[] = $e; }
        }
         
        foreach (self::getZoneSpots('player_popunder') as $s) {
            $parts = array_values(array_intersect(
                array_map('trim', explode(',', (string) ($s['pop_trigger'] ?? ''))),
                ['player_click', 'player_pause', 'player_play']
            ));
            $e = self::popEntry($s, implode(',', $parts ?: ['player_click']));
            if ($e) { $cfg[] = $e; }
        }
        if (empty($cfg)) {
            return;
        }

        $json = json_encode($cfg, JSON_UNESCAPED_SLASHES
            | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

        echo "\n<script id=\"flux-cfg\" type=\"application/json\">" . $json . "</script>\n";
        echo '<script>window.__fluxPreview=' . ($preview ? 'true' : 'false') . ';</script>' . "\n";
        echo '<script src="/assets/js/flux.js?v=3"></script>' . "\n";
    }

     
    private static function popEntry(array $s, string $trig): ?array
    {
        $mode = (($s['pop_mode'] ?? 'url') === 'code') ? 'code' : 'url';
        $entry = [
            'id'    => (int) $s['id'],
            'trig'  => $trig,
            'mode'  => $mode,
            'delay' => max(0, (int) ($s['pop_delay'] ?? 0)),
            'cap'   => max(0, (int) ($s['pop_cap'] ?? 1)),
            'win'   => max(0, (int) ($s['pop_window'] ?? 24)),
        ];
        if ($mode === 'url') {
            $url = trim((string) ($s['popunder_url'] ?? ''));
            if (!preg_match('#^https?://#i', $url)) {
                return null;  
            }
            $entry['url'] = $url;
        } else {
            $code = (string) ($s['ad_code'] ?? '');
            if (trim($code) === '') {
                return null;
            }
            $entry['code'] = base64_encode($code);  
        }
        return $entry;
    }

    

    public static function interstitial(): void
    {
        if (!self::enabled()) {
            return;
        }
        $spot = self::pickSpot('interstitial');
        if (!$spot) {
            return;
        }
        $inner = self::creativeInner($spot);
        if ($inner === '') {
            return;
        }
        self::ensureCss();
        echo '<div class="flux-fs" id="flux-fs" data-flux="interstitial" data-fs-id="' . (int) $spot['id'] . '" style="display:none">'
            . '<div class="flux-fs__box">'
            . self::closeBtn()
            . '<span class="flux__tag">Sponsored</span>'
            . '<div class="flux__body">' . $inner . '</div>'
            . '</div></div>';
        echo '<script>(function(){var el=document.getElementById("flux-fs");if(!el){return;}var K="flux_fs_seen";try{if(sessionStorage.getItem(K)){return;}}catch(e){}setTimeout(function(){try{sessionStorage.setItem(K,"1");}catch(e){}el.style.display="flex";var id=el.getAttribute("data-fs-id");try{navigator.sendBeacon("/iv/"+id);}catch(e){}},2000);el.addEventListener("click",function(e){if(e.target===el){el.style.display="none";}});})();</script>' . "\n";
    }

    

    public static function push(): void
    {
        if (!self::enabled()) {
            return;
        }
        $spot = self::pickSpot('push');
        if (!$spot) {
            return;
        }
        $img = trim((string) ($spot['image_url'] ?? ''));
        if ($img === '') {
            return;  
        }
        self::ensureCss();
        $headline = htmlspecialchars((string) ($spot['headline'] ?: 'Sponsored'), ENT_QUOTES);
        echo '<div class="flux-push" id="flux-push" data-flux="push" data-fs-id="' . (int) $spot['id'] . '" style="display:none">'
            . self::closeBtn()
            . '<a class="flux-push__link" href="/go/' . (int) $spot['id'] . '" target="_blank" rel="nofollow sponsored noopener">'
            . '<img class="flux-push__img" src="' . htmlspecialchars($img, ENT_QUOTES) . '" alt="Advertisement" loading="lazy">'
            . '<span class="flux-push__txt"><span class="flux-push__h">' . $headline . '</span><span class="flux-push__tag">Sponsored</span></span>'
            . '</a></div>';
        echo '<script>(function(){var el=document.getElementById("flux-push");if(!el){return;}var K="flux_push_seen";try{if(sessionStorage.getItem(K)){return;}}catch(e){}setTimeout(function(){try{sessionStorage.setItem(K,"1");}catch(e){}el.style.display="flex";requestAnimationFrame(function(){el.classList.add("flux-push--in");});var id=el.getAttribute("data-fs-id");try{navigator.sendBeacon("/iv/"+id);}catch(e){}},4000);})();</script>' . "\n";
    }

     
    public static function trackPopFire(): void
    {
        $id = (int) Router::param('id');
        if ($id > 0 && (!class_exists('RateLimiter') || RateLimiter::throttle('ad_pop_' . $id, 20, 100))) {
             
            Database::query(
                "UPDATE ad_spots s JOIN ad_zones z ON z.id = s.zone_id
                    SET s.clicks = s.clicks + 1
                  WHERE s.id = ? AND z.slug IN ('popup', 'player_popunder') AND s.is_active = 1",
                [$id]
            );
            self::logEvent($id, 'click');
        }
        http_response_code(204);
        exit;
    }

     
    public static function clickRedirect(): void
    {
        $id = (int) Router::param('id');
        $spot = $id > 0 ? Database::fetchOne('SELECT click_url FROM ad_spots WHERE id = ?', [$id]) : null;
         
        if ($spot && (!class_exists('RateLimiter') || RateLimiter::throttle('ad_click_' . $id, 30, 200))) {
            Database::query('UPDATE ad_spots SET clicks = clicks + 1 WHERE id = ?', [$id]);
            self::logEvent($id, 'click');
        }
        $url = trim((string) ($spot['click_url'] ?? ''));
        if ($url === '' || !preg_match('#^https?://#i', $url)) {
            $url = '/';
        }
        header('Location: ' . $url, true, 302);
        exit;
    }

     

    private static bool $zonesLoaded = false;
    private static bool $spotsLoaded = false;

    private static function getZone(string $slug): ?array
    {
        if (!self::$zonesLoaded) {
            self::$zonesLoaded = true;
            foreach (Database::fetchAll('SELECT id, is_active, slug FROM ad_zones') as $z) {
                self::$zoneCache[$z['slug']] = ((int) $z['is_active'] === 1)
                    ? ['id' => $z['id'], 'is_active' => $z['is_active']]
                    : false;
            }
        }
        return (self::$zoneCache[$slug] ?? false) ?: null;
    }

     
    private static function getZoneSpots(string $slug): array
    {
        $zone = self::getZone($slug);
        if (!$zone) {
            return [];
        }
        $zoneId = (int) $zone['id'];
        if (!self::$spotsLoaded) {
            self::$spotsLoaded = true;
            $now = date('Y-m-d H:i:s');
            $device = self::isMobile() ? 'mobile' : 'desktop';
            $rows = Database::fetchAll(
                "SELECT * FROM ad_spots
                  WHERE is_active = 1
                    AND (device = 'all' OR device = ?)
                    AND (start_date IS NULL OR TIMESTAMP(start_date, COALESCE(start_time, '00:00:00')) <= ?)
                    AND (end_date IS NULL OR TIMESTAMP(end_date, COALESCE(end_time, '23:59:59')) >= ?)
                  ORDER BY position, id",
                [$device, $now, $now]
            );
             
            $cc = self::country();
            $keep = array_values(array_filter($rows, static function ($s) use ($cc) {
                $geo = trim((string) ($s['geo'] ?? ''));
                if ($geo === '') {
                    return true;
                }
                return $cc !== '' && in_array($cc, array_map('trim', explode(',', strtoupper($geo))), true);
            }));
            self::$spotCache = [];
            foreach ($keep as $s) {
                self::$spotCache[(int) $s['zone_id']][] = $s;
            }
        }
        return self::$spotCache[$zoneId] ?? [];
    }

    private static function pickSpot(string $slug): ?array
    {
        $spots = self::getZoneSpots($slug);
        if (empty($spots)) {
            return null;
        }
         
        $paid = array_values(array_filter($spots, static fn($s) => (int) ($s['is_house'] ?? 0) === 0));
        $pool = !empty($paid) ? $paid : array_values($spots);
         
        $total = 0;
        foreach ($pool as $s) {
            $total += max(1, min(10, (int) ($s['weight'] ?? 5)));
        }
        $r = random_int(1, $total);
        $acc = 0;
        foreach ($pool as $s) {
            $acc += max(1, min(10, (int) ($s['weight'] ?? 5)));
            if ($r <= $acc) {
                return $s;
            }
        }
        return $pool[0];
    }

    private static function renderSpot(array $spot, string $format, string $slug = ''): string
    {
        if ($format === 'native') {
            return self::nativeCard($spot, $slug);
        }

        $inner = self::creativeInner($spot);
        if ($inner === '') {
            return '';  
        }

        $z = $slug !== '' ? ' data-flux="' . htmlspecialchars($slug, ENT_QUOTES) . '" data-flux-id="' . (int) $spot['id'] . '"' : '';

        if ($format === 'sticky') {
            return '<div class="flux flux--sticky" id="fluxBar"' . $z . '>'
                 . self::closeBtn()
                 . '<span class="flux__tag">Sponsored</span>' . $inner . '</div>';
        }
        if ($format === 'overlay') {
            return '<div class="flux flux--overlay"' . $z . '>'
                 . self::closeBtn() . $inner . '</div>';
        }
        if ($format === 'pause') {
             
            return '<div class="flux flux--pause"' . $z . ' style="display:none">'
                 . self::closeBtn()
                 . '<span class="flux__tag">Advertisement</span>'
                 . '<div class="flux__body">' . $inner . '</div></div>';
        }

         
        return '<div class="flux flux--' . $format . '"' . $z . '>'
             . self::closeBtn()
             . '<span class="flux__tag">Sponsored</span>'
             . '<div class="flux__body">' . $inner . '</div></div>';
    }

     
    private static function creativeInner(array $spot): string
    {
        $type = $spot['ad_type'] ?? 'code';
        if ($type === 'image' && trim((string) ($spot['image_url'] ?? '')) !== '') {
            return self::imageLink($spot);
        }
        if ($type === 'code' && trim((string) ($spot['ad_code'] ?? '')) !== '') {
            return $spot['ad_code'];
        }
        return '';
    }

    private static function imageLink(array $spot): string
    {
        $img = htmlspecialchars((string) $spot['image_url'], ENT_QUOTES);
        $href = '/go/' . (int) $spot['id'];
        $alt = htmlspecialchars((string) ($spot['headline'] ?: 'Advertisement'), ENT_QUOTES);  
        return '<a class="flux__link" href="' . $href . '" target="_blank" rel="nofollow sponsored noopener">'
             . '<img src="' . $img . '" alt="' . $alt . '" loading="lazy"></a>';
    }

    private static function nativeCard(array $spot, string $slug = ''): string
    {
        $type = $spot['ad_type'] ?? 'image';
        $href = '/go/' . (int) $spot['id'];
        $title = htmlspecialchars((string) ($spot['headline'] ?: 'Sponsored'), ENT_QUOTES);


        $hasCode  = $type === 'code'  && trim((string) ($spot['ad_code'] ?? '')) !== '';
        $hasImage = $type === 'image' && trim((string) ($spot['image_url'] ?? '')) !== '';
        if (!$hasCode && !$hasImage) {
            return '';
        }

        if ($type === 'code' && trim((string) ($spot['ad_code'] ?? '')) !== '') {
            $media = '<div class="video-card__thumb">' . $spot['ad_code'] . '<span class="video-card__flux-tag">Sponsored</span></div>';
            $linkTitle = $title;
        } else {
            $img = htmlspecialchars((string) ($spot['image_url'] ?: ''), ENT_QUOTES);
            $media = '<a class="video-card__thumb" href="' . $href . '" target="_blank" rel="nofollow sponsored noopener">'
                   . ($img !== '' ? '<img src="' . $img . '" alt="' . $title . '" loading="lazy">' : '')
                   . '<span class="video-card__flux-tag">Sponsored</span></a>';
            $linkTitle = '<a href="' . $href . '" target="_blank" rel="nofollow sponsored noopener">' . $title . '</a>';
        }

        $z = ' data-flux-id="' . (int) $spot['id'] . '"' . ($slug !== '' ? ' data-flux="' . htmlspecialchars($slug, ENT_QUOTES) . '"' : '');
        return '<article class="video-card video-card--flux"' . $z . '>' . self::closeBtn() . $media
             . '<div class="video-card__info"><h3 class="video-card__title">' . $linkTitle . '</h3>'
             . '<div class="video-card__meta"><span>Sponsored</span><span>Promoted</span></div></div></article>';
    }

    private static function trackImpression(int $id): void
    {
        try {
            Database::query('UPDATE ad_spots SET impressions = impressions + 1 WHERE id = ?', [$id]);
        } catch (\Throwable) {
        }
    }

    private static function isMobile(): bool
    {
        $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
        return $ua !== '' && preg_match('/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Opera Mini/i', $ua) === 1;
    }

     
    private static function country(): string
    {
        $c = strtoupper(substr((string) ($_SERVER['HTTP_CF_IPCOUNTRY'] ?? ''), 0, 2));
        return preg_match('/^[A-Z]{2}$/', $c) ? $c : '';
    }

     
    private static function accentColor(): string
    {
        $c = trim((string) Setting::get('ads_accent_color', '#dc2626'));
        return preg_match('/^#[0-9a-fA-F]{6}$/', $c) ? $c : '#dc2626';
    }

     
    private static function closeable(): bool
    {
        return Setting::get('ads_closeable', '1') === '1';
    }

     
    private static function closeBtn(): string
    {
        return self::closeable()
            ? '<button type="button" class="flux-x" aria-label="Close ad" title="Close">&times;</button>'
            : '';
    }

    private static function ensureCss(): void
    {
        if (self::$cssDone) {
            return;
        }
        self::$cssDone = true;
        echo '<style id="flux-css">'
            . ':root{--flux-accent:' . self::accentColor() . ';}'
            . '.flux{position:relative;display:flex;flex-direction:column;align-items:center;width:100%;max-width:100%;min-width:0;}'
            . '.flux__tag{font-size:9px;line-height:1;letter-spacing:.08em;text-transform:uppercase;color:var(--flux-accent,#dc2626);opacity:.85;margin-bottom:4px;align-self:center;}'
            . '.flux__body{display:flex;justify-content:center;width:100%;max-width:100%;min-width:0;overflow:hidden;}'
            . '.flux--leaderboard .flux__body,.flux--billboard .flux__body,.flux--rectangle .flux__body{contain:inline-size;}'
            . '.flux__link{display:inline-flex;max-width:100%;}'
            . '.flux__link img{display:block;max-width:100%;height:auto;border-radius:var(--radius,8px);}'
            . '.flux--leaderboard{padding:14px var(--container-padding,24px);}'
            . '.flux--leaderboard .flux__link img,.flux--billboard .flux__link img{width:100%;}'
            . '.flux--leaderboard .flux__body{max-width:728px;margin:0 auto;}'
            . '.flux--billboard{padding:18px var(--container-padding,24px) 4px;}'
            . '.flux--billboard .flux__body{max-width:970px;margin:0 auto;}'
            . '.flux--rectangle{margin:0 0 16px;}'
            . '.flux--rectangle .flux__body{max-width:300px;}'
            . '.flux--rectangle .flux__link img{width:300px;max-width:100%;}'
             
            . '.flux-hero{position:relative;display:flex;flex-direction:column;align-items:center;padding:18px var(--container-padding,24px) 4px;}'
            . '.flux-hero__row{display:flex;align-items:center;justify-content:center;gap:18px;width:100%;height:clamp(150px,15vw,230px);}'
            . '.flux-hero__center{height:100%;display:flex;align-items:center;justify-content:center;min-width:0;overflow:hidden;}'
            . '.flux-hero__center .flux__link{display:flex;height:100%;max-width:100%;}'
            . '.flux-hero__center img{display:block;height:100%;width:auto;max-width:100%;object-fit:contain;border-radius:var(--radius,8px);}'
            . '.flux-hero__side{height:100%;aspect-ratio:1/1;flex:0 0 auto;display:flex;min-width:0;overflow:hidden;}'
            . '.flux-hero__side .flux__link,.flux-hero__side a{display:flex;width:100%;height:100%;}'
            . '.flux-hero__side img{display:block;height:100%;width:100%;object-fit:cover;border-radius:var(--radius,8px);}'
            . '@media(max-width:1200px){.flux-hero__side{display:none;}.flux-hero__row{height:auto;}.flux-hero__center{flex:1 1 100%;width:100%;}.flux-hero__center .flux__link{width:100%;}.flux-hero__center img{height:auto;width:100%;max-width:970px;}}'
             
            . '.flux--sticky{position:fixed;left:0;right:0;bottom:0;z-index:60;display:none;flex-direction:row;justify-content:center;align-items:center;padding:5px 0;background:rgba(8,12,24,.92);box-shadow:0 -2px 12px rgba(0,0,0,.35);max-width:100vw;overflow:hidden;}'
            . '.flux--sticky .flux__tag{position:absolute;left:6px;top:50%;transform:translateY(-50%);margin:0;}'
            . '.flux--sticky img{max-width:320px;height:50px;width:auto;border-radius:4px;}'
            . '@media(max-width:768px){.flux--sticky{display:flex;}body:has(#fluxBar){padding-bottom:64px;}}'
             
            . '.flux--overlay{position:absolute;left:50%;bottom:60px;transform:translateX(-50%);z-index:2147483000;display:flex;max-width:90%;pointer-events:auto;}'
            . '.flux--overlay img{max-width:min(100%,728px);height:auto;border-radius:6px;box-shadow:0 6px 24px rgba(0,0,0,.5);}'
            . '.flux--pause{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:2147483000;width:auto;flex-direction:column;align-items:center;max-width:86%;pointer-events:auto;}'
            . '.flux--pause .flux__tag{background:rgba(0,0,0,.55);color:#fff;opacity:.9;padding:2px 8px;border-radius:4px;margin-bottom:6px;}'
            . '.flux--pause .flux__body{overflow:visible;}'
            . '.flux--pause .flux__body img{max-width:min(100%,440px);height:auto;border-radius:8px;box-shadow:0 10px 40px rgba(0,0,0,.6);}'
            . '.flux-fs{position:fixed;inset:0;z-index:2147483600;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.82);padding:20px;}'
            . '.flux-fs__box{position:relative;max-width:min(96vw,900px);display:flex;flex-direction:column;align-items:center;}'
            . '.flux-fs__box .flux__body{overflow:visible;}'
            . '.flux-fs__box .flux__body img{max-width:100%;max-height:80vh;height:auto;border-radius:8px;box-shadow:0 12px 48px rgba(0,0,0,.6);}'
            . '.flux-fs .flux-x{top:-12px;right:-12px;width:30px;height:30px;line-height:28px;font-size:17px;background:#fff;color:#111;opacity:1;box-shadow:0 2px 8px rgba(0,0,0,.4);}'
            . '.flux-fs .flux__tag{color:#fff;opacity:.85;margin-bottom:8px;}'
            . '.flux-push{position:fixed;left:16px;bottom:16px;z-index:2147483400;display:none;align-items:center;width:320px;max-width:calc(100vw - 32px);background:#fff;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.35);padding:10px;transform:translateY(160%);transition:transform .45s ease;}'
            . '.flux-push--in{transform:translateY(0);}'
            . '.flux-push__link{display:flex;align-items:center;gap:10px;text-decoration:none;width:100%;}'
            . '.flux-push__img{width:56px;height:56px;border-radius:8px;object-fit:cover;flex:0 0 auto;}'
            . '.flux-push__txt{display:flex;flex-direction:column;min-width:0;}'
            . '.flux-push__h{font-size:13px;font-weight:600;color:#111;line-height:1.25;}'
            . '.flux-push__tag{font-size:10px;color:#8a8a8a;text-transform:uppercase;letter-spacing:.04em;}'
            . '.flux-push .flux-x{top:4px;right:4px;}'
            . '@media(max-width:768px){.flux-push{left:8px;right:8px;width:auto;}}'
             
            . '.video-card--flux .video-card__thumb{display:flex;align-items:center;justify-content:center;background:var(--bg-card,#1e293b);min-width:0;overflow:hidden;}'
            . '.video-card--flux .video-card__thumb img{width:100%;height:100%;object-fit:cover;}'
            . '.video-card__flux-tag{position:absolute;top:6px;left:6px;z-index:2;font-size:9px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#fff;background:var(--flux-accent,#dc2626);padding:2px 6px;border-radius:4px;}'
            . '.video-card--flux{position:relative;}'
            . '.flux-x{position:absolute;top:5px;right:5px;width:18px;height:18px;line-height:16px;text-align:center;border:none;border-radius:50%;background:rgba(0,0,0,.5);color:#fff;font-size:13px;cursor:pointer;z-index:5;padding:0;opacity:.6;transition:opacity .15s;}'
            . '.flux-x:hover{opacity:1;}'
            . '.flux--sticky .flux-x{top:50%;right:6px;transform:translateY(-50%);}'
            . '</style>';
        echo '<script>(function(){var K="flux_x",c=[];try{c=JSON.parse(sessionStorage.getItem(K)||"[]");}catch(e){}if(!(c instanceof Array)){c=[];}function hide(){var n=document.querySelectorAll("[data-flux]");for(var i=0;i<n.length;i++){if(c.indexOf(n[i].getAttribute("data-flux"))>=0){n[i].style.display="none";}}}document.addEventListener("click",function(e){var b=e.target&&e.target.closest?e.target.closest(".flux-x"):null;if(!b){return;}e.preventDefault();e.stopPropagation();var h=b.closest("[data-flux]");if(!h){return;}var s=h.getAttribute("data-flux");if(c.indexOf(s)<0){c.push(s);try{sessionStorage.setItem(K,JSON.stringify(c));}catch(e){}}h.style.display="none";},true);hide();if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",hide);}if("IntersectionObserver" in window){var io=new IntersectionObserver(function(es){for(var i=0;i<es.length;i++){var e=es[i],el=e.target,id=el.getAttribute("data-flux-id");if(!id){continue;}if(e.isIntersecting&&e.intersectionRatio>=0.5){if(!el.__fs&&!el.__ft){el.__ft=setTimeout((function(el,id){return function(){el.__fs=1;io.unobserve(el);try{navigator.sendBeacon("/iv/"+id);}catch(e){}};})(el,id),1000);}}else if(el.__ft){clearTimeout(el.__ft);el.__ft=null;}}},{threshold:[0,0.5,1]});function obs(){var n=document.querySelectorAll("[data-flux-id]");for(var i=0;i<n.length;i++){io.observe(n[i]);}}obs();if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",obs);}}})();</script>';
        echo '<script>(function(){var SEL=".flux__body,.flux--sticky,.flux-hero__center,.flux-hero__side,.video-card--flux .video-card__thumb";function creatives(b){var o=[],c=b.children,i,e,n;for(i=0;i<c.length;i++){e=c[i];n=e.tagName;if(n==="SCRIPT"||n==="STYLE"||n==="LINK"||n==="NOSCRIPT"){continue;}if(e.classList&&(e.classList.contains("flux-x")||e.classList.contains("flux__tag")||e.classList.contains("video-card__flux-tag"))){continue;}o.push(e);}return o;}function fit(b){var av=b.clientWidth;if(!av){return;}var k=creatives(b),nw=0,i,e,h,r;for(i=0;i<k.length;i++){if(k[i].offsetWidth>nw){nw=k[i].offsetWidth;}}if(!nw){return;}if(nw<=av+1){for(i=0;i<k.length;i++){e=k[i];if(e.getAttribute("data-flux-fit")){e.style.transform="";e.style.transformOrigin="";e.style.marginBottom="";e.removeAttribute("data-flux-fit");}}return;}r=Math.floor(av/nw*1000)/1000;for(i=0;i<k.length;i++){e=k[i];h=e.offsetHeight;e.style.transformOrigin="top center";e.style.transform="scale("+r+")";e.style.marginBottom=(-Math.round(h*(1-r)))+"px";e.setAttribute("data-flux-fit","1");}}function fitAll(){var n=document.querySelectorAll(SEL),i;for(i=0;i<n.length;i++){fit(n[i]);}}var t=null;function soon(){if(t){clearTimeout(t);}t=setTimeout(function(){t=null;fitAll();},80);}var watching=false;function watch(){if(watching||!window.MutationObserver||!document.body){return;}watching=true;new MutationObserver(soon).observe(document.body,{childList:true,subtree:true});}fitAll();watch();document.addEventListener("DOMContentLoaded",function(){fitAll();watch();});window.addEventListener("load",fitAll);window.addEventListener("resize",soon);window.addEventListener("orientationchange",soon);setTimeout(fitAll,400);setTimeout(fitAll,1500);setTimeout(fitAll,4000);})();</script>';
    }
}
