<?php
declare(strict_types=1);

require_once __DIR__ . '/../../includes/start.php';
require_login();
require_once __DIR__ . '/_lib.php';

/** @var PDO|null $pdo */
/** @var array|null $me */
$pdo = (isset($pdo) && $pdo instanceof PDO) ? $pdo : db();
$me  = $me ?? current_user();
$meId = (int)($me['id'] ?? 0);

if (!function_exists('h')) {
    function h(?string $s): string
    {
        return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
    }
}

$GLOBALS['_RC_LANG'] = (function () use ($me): string {
    $lang = function_exists('lang') ? (string)lang() : (string)($_SESSION['lang'] ?? ($me['lang'] ?? 'uk'));
    return in_array($lang, ['uk', 'ru', 'en'], true) ? $lang : 'uk';
})();

if (!function_exists('rc_lang')) {
    function rc_lang(): string
    {
        $lang = (string)($GLOBALS['_RC_LANG'] ?? 'uk');
        return in_array($lang, ['uk', 'ru', 'en'], true) ? $lang : 'uk';
    }
}

if (!function_exists('rc_t')) {
    function rc_t(string $uk, string $ru, string $en): string
    {
        return match (rc_lang()) {
            'ru' => $ru,
            'en' => $en,
            default => $uk,
        };
    }
}

if (!function_exists('rc_num')) {
    function rc_num(int|float|string $n): string
    {
        return number_format((float)$n, 0, '.', ' ');
    }
}

if (!function_exists('rc_flash_set')) {
    function rc_flash_set(string $type, string $text): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            @session_start();
        }
        $_SESSION['_reclama_flash'] = [
            'type' => $type,
            'text' => $text,
        ];
    }
}

if (!function_exists('rc_flash_get')) {
    function rc_flash_get(): ?array
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            @session_start();
        }
        $flash = $_SESSION['_reclama_flash'] ?? null;
        unset($_SESSION['_reclama_flash']);
        return is_array($flash) ? $flash : null;
    }
}

if (!function_exists('rc_redirect')) {
    function rc_redirect(string $to): void
    {
        if (function_exists('redirect')) {
            redirect($to);
            return;
        }
        header('Location: ' . $to, true, (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') ? 303 : 302);
        exit;
    }
}

if (!function_exists('rc_csrf_token')) {
    function rc_csrf_token(): string
    {
        if (function_exists('csrf_token')) {
            try {
                return (string)csrf_token();
            } catch (Throwable $e) {
            }
        }

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

        $key = '_csrf';
        if (empty($_SESSION[$key]) || !is_string($_SESSION[$key])) {
            try {
                $_SESSION[$key] = bin2hex(random_bytes(16));
            } catch (Throwable $e) {
                $_SESSION[$key] = sha1((string)microtime(true) . '|' . (string)mt_rand());
            }
        }

        return (string)$_SESSION[$key];
    }
}

if (!function_exists('rc_csrf_ok')) {
    function rc_csrf_ok(?string $token): bool
    {
        $token = trim((string)$token);
        if ($token === '') return false;

        if (function_exists('csrf_check')) {
            try {
                return (bool)csrf_check($token);
            } catch (Throwable $e) {
            }
        }

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

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

if (!function_exists('rc_table_exists')) {
    function rc_table_exists(PDO $pdo, string $table): bool
    {
        return reclama_table_exists($pdo, $table);
    }
}

if (!function_exists('rc_col_exists')) {
    function rc_col_exists(PDO $pdo, string $table, string $column): bool
    {
        return reclama_column_exists($pdo, $table, $column);
    }
}

if (!function_exists('rc_module_enabled')) {
    function rc_module_enabled(PDO $pdo): bool
    {
        return reclama_is_enabled($pdo) && rc_table_exists($pdo, 'reclama_campaigns');
    }
}

if (!function_exists('rc_status_label')) {
    function rc_status_label(string $status): string
    {
        return match (trim($status)) {
            'draft'    => rc_t('Чернетка', 'Черновик', 'Draft'),
            'pending'  => rc_t('На перевірці', 'На проверке', 'Pending'),
            'approved' => rc_t('Схвалено', 'Одобрено', 'Approved'),
            'rejected' => rc_t('Відхилено', 'Отклонено', 'Rejected'),
            'active'   => rc_t('Активна', 'Активна', 'Active'),
            'paused'   => rc_t('Пауза', 'Пауза', 'Paused'),
            'finished' => rc_t('Завершена', 'Завершена', 'Finished'),
            'canceled' => rc_t('Скасована', 'Отменена', 'Canceled'),
            default    => trim($status) !== '' ? trim($status) : rc_t('Невідомо', 'Неизвестно', 'Unknown'),
        };
    }
}

if (!function_exists('rc_status_badge')) {
    function rc_status_badge(string $status): string
    {
        return match (trim($status)) {
            'draft'    => 'secondary',
            'pending'  => 'warning text-dark',
            'approved' => 'info text-dark',
            'rejected' => 'danger',
            'active'   => 'success',
            'paused'   => 'secondary',
            'finished' => 'dark',
            'canceled' => 'light text-dark',
            default    => 'secondary',
        };
    }
}

if (!function_exists('rc_payment_label')) {
    function rc_payment_label(string $status): string
    {
        return match (trim($status)) {
            'unpaid'         => rc_t('Не оплачено', 'Не оплачено', 'Unpaid'),
            'reserved'       => rc_t('Зарезервовано', 'Зарезервировано', 'Reserved'),
            'paid'           => rc_t('Оплачено', 'Оплачено', 'Paid'),
            'refunded'       => rc_t('Повернено', 'Возвращено', 'Refunded'),
            'partial_refund' => rc_t('Часткове повернення', 'Частичный возврат', 'Partial refund'),
            default          => trim($status) !== '' ? trim($status) : rc_t('Невідомо', 'Неизвестно', 'Unknown'),
        };
    }
}

if (!function_exists('rc_payment_badge')) {
    function rc_payment_badge(string $status): string
    {
        return match (trim($status)) {
            'paid'           => 'success',
            'reserved'       => 'info text-dark',
            'partial_refund' => 'warning text-dark',
            'refunded'       => 'light text-dark',
            'unpaid'         => 'danger',
            default          => 'secondary',
        };
    }
}

if (!function_exists('rc_moderation_label')) {
    function rc_moderation_label(string $status): string
    {
        return match (trim($status)) {
            'pending'  => rc_t('Очікує модерації', 'Ожидает модерации', 'Pending moderation'),
            'approved' => rc_t('Модерацію пройдено', 'Модерация пройдена', 'Approved'),
            'rejected' => rc_t('Модерацію не пройдено', 'Модерация не пройдена', 'Rejected'),
            default    => trim($status) !== '' ? trim($status) : rc_t('Невідомо', 'Неизвестно', 'Unknown'),
        };
    }
}

if (!function_exists('rc_excerpt')) {
    function rc_excerpt(?string $text, int $len = 180): string
    {
        $text = trim(preg_replace('/\s+/u', ' ', (string)$text) ?? '');
        if ($text === '') return '';
        return (mb_strlen($text) > $len) ? (mb_substr($text, 0, $len - 1) . '…') : $text;
    }
}

if (!function_exists('rc_daily_stats_columns')) {
    function rc_daily_stats_columns(PDO $pdo): array
    {
        return reclama_daily_stats_columns($pdo);
    }
}

if (!function_exists('rc_campaign_daily_series')) {
    function rc_campaign_daily_series(PDO $pdo, int $campaignId, ?string $startDate = null, ?string $endDate = null, int $defaultDays = 30): array
    {
        if (function_exists('reclama_campaign_daily_series')) {
            return reclama_campaign_daily_series($pdo, $campaignId, $startDate, $endDate, $defaultDays);
        }

        return [
            'start_date' => '',
            'end_date'   => '',
            'days_count' => 0,
            'days'       => [],
            'totals'     => [
                'impressions_total'    => 0,
                'clicks_total'         => 0,
                'unique_viewers_total' => 0,
                'ctr'                  => 0.0,
            ],
        ];
    }
}

if (!function_exists('rc_sync_user_campaign_statuses')) {
    function rc_sync_user_campaign_statuses(PDO $pdo, int $userId, int $limit = 200): void
    {
        if ($userId <= 0 || !rc_table_exists($pdo, 'reclama_campaigns')) {
            return;
        }

        $limit = max(1, min(500, $limit));

        try {
            $st = $pdo->prepare(
                'SELECT id
                 FROM reclama_campaigns
                 WHERE user_id = ?
                   AND status IN ("pending", "approved", "active")
                 ORDER BY id DESC
                 LIMIT ' . (int)$limit
            );
            $st->execute([$userId]);
            $ids = $st->fetchAll(PDO::FETCH_COLUMN) ?: [];
        } catch (Throwable $e) {
            return;
        }

        foreach ($ids as $campaignId) {
            $campaignId = (int)$campaignId;
            if ($campaignId > 0) {
                try {
                    reclama_sync_campaign_status($pdo, $campaignId);
                } catch (Throwable $e) {
                }
            }
        }
    }
}

if (!function_exists('rc_targeting_defaults')) {
    function rc_targeting_defaults(): array
    {
        return [
            'country_code'           => null,
            'exclude_countries'      => '',
            'exclude_countries_list' => [],
            'exclude_countries_json' => null,
            'city'                   => null,
            'gender'                 => 'any',
            'age_from'               => null,
            'age_to'                 => null,
            'lang_code'              => 'any',
            'device_type'            => 'all',
            'auth_scope'             => 'all',
            'segment_codes'          => '',
            'segment_codes_list'     => [],
            'segment_codes_json'     => null,
        ];
    }
}

if (!function_exists('rc_get_targeting')) {
    function rc_get_targeting(PDO $pdo, int $campaignId): array
    {
        $defaults = rc_targeting_defaults();

        if ($campaignId <= 0 || !rc_table_exists($pdo, 'reclama_campaign_targeting')) {
            return $defaults;
        }

        $select = ['campaign_id'];

        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'country_code')) {
            $select[] = 'country_code';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'exclude_countries_json')) {
            $select[] = 'exclude_countries_json';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'city')) {
            $select[] = 'city';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'gender')) {
            $select[] = 'gender';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'age_from')) {
            $select[] = 'age_from';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'age_to')) {
            $select[] = 'age_to';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'lang_code')) {
            $select[] = 'lang_code';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'device_type')) {
            $select[] = 'device_type';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'auth_scope')) {
            $select[] = 'auth_scope';
        }
        if (rc_col_exists($pdo, 'reclama_campaign_targeting', 'segment_codes_json')) {
            $select[] = 'segment_codes_json';
        }

        try {
            $st = $pdo->prepare('SELECT ' . implode(', ', $select) . ' FROM reclama_campaign_targeting WHERE campaign_id = ? LIMIT 1');
            $st->execute([$campaignId]);
            $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$row) return $defaults;

            $excludeCountriesList = reclama_normalize_country_codes_list($row['exclude_countries_json'] ?? null);
            $segmentCodesList     = reclama_normalize_segment_codes($row['segment_codes_json'] ?? null);
            $authScope            = trim((string)($row['auth_scope'] ?? 'all'));
            if (!in_array($authScope, ['all', 'guests', 'users'], true)) {
                $authScope = 'all';
            }

            return array_merge($defaults, [
                'country_code'           => $row['country_code'] ?? null,
                'exclude_countries'      => implode(', ', $excludeCountriesList),
                'exclude_countries_list' => $excludeCountriesList,
                'exclude_countries_json' => $row['exclude_countries_json'] ?? reclama_json_encode_or_null($excludeCountriesList),
                'city'                   => $row['city'] ?? null,
                'gender'                 => $row['gender'] ?? 'any',
                'age_from'               => $row['age_from'] ?? null,
                'age_to'                 => $row['age_to'] ?? null,
                'lang_code'              => $row['lang_code'] ?? 'any',
                'device_type'            => $row['device_type'] ?? 'all',
                'auth_scope'             => $authScope,
                'segment_codes'          => implode(', ', $segmentCodesList),
                'segment_codes_list'     => $segmentCodesList,
                'segment_codes_json'     => $row['segment_codes_json'] ?? reclama_json_encode_or_null($segmentCodesList),
            ]);
        } catch (Throwable $e) {
            return $defaults;
        }
    }
}

if (!function_exists('rc_save_targeting')) {
    function rc_save_targeting(PDO $pdo, int $campaignId, array $targeting): void
    {
        if ($campaignId <= 0 || !rc_table_exists($pdo, 'reclama_campaign_targeting')) {
            return;
        }

        $v = reclama_normalize_targeting($targeting);

        $columns = ['campaign_id'];
        $placeholders = ['?'];
        $bind = [$campaignId];
        $updates = [];

        $map = [
            'country_code'           => 'country_code',
            'exclude_countries_json' => 'exclude_countries_json',
            'city'                   => 'city',
            'gender'                 => 'gender',
            'age_from'               => 'age_from',
            'age_to'                 => 'age_to',
            'lang_code'              => 'lang_code',
            'device_type'            => 'device_type',
            'auth_scope'             => 'auth_scope',
            'segment_codes_json'     => 'segment_codes_json',
        ];

        foreach ($map as $field => $column) {
            if (!rc_col_exists($pdo, 'reclama_campaign_targeting', $column)) {
                continue;
            }

            $columns[] = $column;
            $placeholders[] = '?';
            $bind[] = $v[$field] ?? null;
            $updates[] = $column . ' = VALUES(' . $column . ')';
        }

        if (count($columns) <= 1) {
            return;
        }

        $sql = '
            INSERT INTO reclama_campaign_targeting
                (' . implode(', ', $columns) . ')
            VALUES
                (' . implode(', ', $placeholders) . ')
            ON DUPLICATE KEY UPDATE
                ' . implode(', ', $updates);

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

if (!function_exists('rc_user_stats')) {
    function rc_user_stats(PDO $pdo, int $userId): array
    {
        if (function_exists('reclama_user_dashboard_stats')) {
            $stats = reclama_user_dashboard_stats($pdo, $userId);
            return [
                'total'        => (int)($stats['total'] ?? 0),
                'draft'        => (int)($stats['draft'] ?? 0),
                'pending'      => (int)($stats['pending'] ?? 0),
                'approved'     => (int)($stats['approved'] ?? 0),
                'active'       => (int)($stats['active'] ?? 0),
                'paused'       => (int)($stats['paused'] ?? 0),
                'finished'     => (int)($stats['finished'] ?? 0),
                'rejected'     => (int)($stats['rejected'] ?? 0),
                'canceled'     => (int)($stats['canceled'] ?? 0),
                'budget_total' => (int)($stats['budget_total'] ?? 0),
                'budget_paid'  => (int)($stats['budget_paid'] ?? 0),
                'impressions'  => (int)($stats['impressions_total'] ?? 0),
                'clicks'       => (int)($stats['clicks_total'] ?? 0),
            ];
        }

        $stats = [
            'total' => 0,
            'draft' => 0,
            'pending' => 0,
            'approved' => 0,
            'active' => 0,
            'paused' => 0,
            'finished' => 0,
            'rejected' => 0,
            'canceled' => 0,
            'budget_total' => 0,
            'budget_paid' => 0,
            'impressions' => 0,
            'clicks' => 0,
        ];

        if ($userId <= 0 || !rc_table_exists($pdo, 'reclama_campaigns')) {
            return $stats;
        }

        try {
            $st = $pdo->prepare('
                SELECT
                    COUNT(*) AS total,
                    SUM(CASE WHEN status = "draft" THEN 1 ELSE 0 END) AS draft_cnt,
                    SUM(CASE WHEN status = "pending" THEN 1 ELSE 0 END) AS pending_cnt,
                    SUM(CASE WHEN status = "approved" THEN 1 ELSE 0 END) AS approved_cnt,
                    SUM(CASE WHEN status = "active" THEN 1 ELSE 0 END) AS active_cnt,
                    SUM(CASE WHEN status = "paused" THEN 1 ELSE 0 END) AS paused_cnt,
                    SUM(CASE WHEN status = "finished" THEN 1 ELSE 0 END) AS finished_cnt,
                    SUM(CASE WHEN status = "rejected" THEN 1 ELSE 0 END) AS rejected_cnt,
                    SUM(CASE WHEN status = "canceled" THEN 1 ELSE 0 END) AS canceled_cnt,
                    COALESCE(SUM(total_price), 0) AS budget_total,
                    COALESCE(SUM(CASE WHEN payment_status IN ("paid","reserved","partial_refund","refunded") THEN total_price ELSE 0 END), 0) AS budget_paid
                FROM reclama_campaigns
                WHERE user_id = ?
            ');
            $st->execute([$userId]);
            $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            $stats['total']        = (int)($row['total'] ?? 0);
            $stats['draft']        = (int)($row['draft_cnt'] ?? 0);
            $stats['pending']      = (int)($row['pending_cnt'] ?? 0);
            $stats['approved']     = (int)($row['approved_cnt'] ?? 0);
            $stats['active']       = (int)($row['active_cnt'] ?? 0);
            $stats['paused']       = (int)($row['paused_cnt'] ?? 0);
            $stats['finished']     = (int)($row['finished_cnt'] ?? 0);
            $stats['rejected']     = (int)($row['rejected_cnt'] ?? 0);
            $stats['canceled']     = (int)($row['canceled_cnt'] ?? 0);
            $stats['budget_total'] = (int)($row['budget_total'] ?? 0);
            $stats['budget_paid']  = (int)($row['budget_paid'] ?? 0);
        } catch (Throwable $e) {
        }

        if (rc_table_exists($pdo, 'reclama_campaign_daily_stats')) {
            $cols = rc_daily_stats_columns($pdo);
            $impCol = $cols['impressions'] ?? null;
            $clkCol = $cols['clicks'] ?? null;

            if ($impCol !== null || $clkCol !== null) {
                $impExpr = $impCol !== null ? 'COALESCE(SUM(ds.`' . $impCol . '`), 0)' : '0';
                $clkExpr = $clkCol !== null ? 'COALESCE(SUM(ds.`' . $clkCol . '`), 0)' : '0';

                try {
                    $sql = '
                        SELECT ' . $impExpr . ' AS impressions, ' . $clkExpr . ' AS clicks
                        FROM reclama_campaign_daily_stats ds
                        INNER JOIN reclama_campaigns c ON c.id = ds.campaign_id
                        WHERE c.user_id = ?
                    ';
                    $st = $pdo->prepare($sql);
                    $st->execute([$userId]);
                    $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    $stats['impressions'] = (int)($row['impressions'] ?? 0);
                    $stats['clicks']      = (int)($row['clicks'] ?? 0);
                } catch (Throwable $e) {
                }
            }
        }

        return $stats;
    }
}

if (!function_exists('rc_user_dashboard_stats')) {
    function rc_user_dashboard_stats(PDO $pdo, int $userId): array
    {
        if (function_exists('reclama_user_dashboard_stats')) {
            return reclama_user_dashboard_stats($pdo, $userId);
        }

        $base = rc_user_stats($pdo, $userId);

        return [
            'total'                   => (int)($base['total'] ?? 0),
            'draft'                   => (int)($base['draft'] ?? 0),
            'pending'                 => (int)($base['pending'] ?? 0),
            'approved'                => (int)($base['approved'] ?? 0),
            'active'                  => (int)($base['active'] ?? 0),
            'paused'                  => (int)($base['paused'] ?? 0),
            'finished'                => (int)($base['finished'] ?? 0),
            'rejected'                => (int)($base['rejected'] ?? 0),
            'canceled'                => (int)($base['canceled'] ?? 0),
            'budget_total'            => (int)($base['budget_total'] ?? 0),
            'budget_paid'             => (int)($base['budget_paid'] ?? 0),
            'impressions_total'       => (int)($base['impressions'] ?? 0),
            'clicks_total'            => (int)($base['clicks'] ?? 0),
            'unique_viewers_total'    => 0,
            'campaigns_with_imps'     => 0,
            'campaigns_with_clicks'   => 0,
            'ctr'                     => 0.0,
            'coins_gross_spent'       => 0,
            'coins_refunded'          => 0,
            'coins_net_spent'         => 0,
        ];
    }
}

if (!function_exists('rc_user_campaigns')) {
    function rc_user_campaigns(PDO $pdo, int $userId, string $statusFilter = 'all', int $limit = 50): array
    {
        if ($userId <= 0 || !rc_table_exists($pdo, 'reclama_campaigns')) {
            return [];
        }

        rc_sync_user_campaign_statuses($pdo, $userId, max($limit, 200));

        $limit = max(1, min(200, $limit));
        $hasTarget = rc_table_exists($pdo, 'reclama_campaign_targeting');
        $hasPosts  = rc_table_exists($pdo, 'posts');

        $where = ['c.user_id = ?'];
        $bind  = [$userId];

        $allowed = ['draft','pending','approved','active','paused','finished','rejected','canceled'];
        if (in_array($statusFilter, $allowed, true)) {
            $where[] = 'c.status = ?';
            $bind[]  = $statusFilter;
        }

        $targetSelect = '
                NULL AS country_code,
                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,
        ';

        if ($hasTarget) {
            $targetCols = [];

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'country_code')
                ? 't.country_code AS country_code'
                : 'NULL AS country_code';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'exclude_countries_json')
                ? 't.exclude_countries_json AS exclude_countries_json'
                : 'NULL AS exclude_countries_json';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'city')
                ? 't.city AS city'
                : 'NULL AS city';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'gender')
                ? 't.gender AS gender'
                : '"any" AS gender';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'age_from')
                ? 't.age_from AS age_from'
                : 'NULL AS age_from';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'age_to')
                ? 't.age_to AS age_to'
                : 'NULL AS age_to';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'lang_code')
                ? 't.lang_code AS lang_code'
                : '"any" AS lang_code';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'device_type')
                ? 't.device_type AS device_type'
                : '"all" AS device_type';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'auth_scope')
                ? 't.auth_scope AS auth_scope'
                : '"all" AS auth_scope';

            $targetCols[] = rc_col_exists($pdo, 'reclama_campaign_targeting', 'segment_codes_json')
                ? 't.segment_codes_json AS segment_codes_json'
                : 'NULL AS segment_codes_json';

            $targetSelect = implode(",\n                ", $targetCols) . ',';
        }

        $statsSql = function_exists('reclama_campaign_stats_source_sql')
            ? reclama_campaign_stats_source_sql($pdo)
            : null;

        $statsSelect = '
                0 AS impressions_total,
                0 AS clicks_total,
                0 AS unique_viewers_total
        ';
        $statsJoin = '';

        if ($statsSql !== null) {
            $statsSelect = '
                COALESCE(ds.impressions_total, 0) AS impressions_total,
                COALESCE(ds.clicks_total, 0) AS clicks_total,
                COALESCE(ds.unique_viewers_total, 0) AS unique_viewers_total
            ';
            $statsJoin = '
                LEFT JOIN (' . $statsSql . ') ds ON ds.campaign_id = c.id
            ';
        }

        $sql = '
            SELECT
                c.*,
                ' . $targetSelect .
                ($hasPosts ? '
                p.body AS source_post_body,' : '
                NULL AS source_post_body,') . '
                ' . $statsSelect . '
            FROM reclama_campaigns c
            ' . ($hasTarget ? 'LEFT JOIN reclama_campaign_targeting t ON t.campaign_id = c.id' : '') . '
            ' . ($hasPosts ? 'LEFT JOIN posts p ON p.id = c.source_post_id' : '') . '
            ' . $statsJoin . '
            WHERE ' . implode(' AND ', $where) . '
            ORDER BY c.id DESC
            LIMIT ' . (int)$limit;

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

            foreach ($rows as &$row) {
                $imp = (int)($row['impressions_total'] ?? 0);
                $clk = (int)($row['clicks_total'] ?? 0);
                $row['ctr'] = function_exists('reclama_calc_ctr')
                    ? reclama_calc_ctr($imp, $clk)
                    : ($imp > 0 ? round(($clk / $imp) * 100, 2) : 0.0);

                $excludeCountriesList = reclama_normalize_country_codes_list($row['exclude_countries_json'] ?? null);
                $segmentCodesList     = reclama_normalize_segment_codes($row['segment_codes_json'] ?? null);

                $row['exclude_countries_list'] = $excludeCountriesList;
                $row['exclude_countries']      = implode(', ', $excludeCountriesList);
                $row['segment_codes_list']     = $segmentCodesList;
                $row['segment_codes']          = implode(', ', $segmentCodesList);

                $authScope = trim((string)($row['auth_scope'] ?? 'all'));
                $row['auth_scope'] = in_array($authScope, ['all', 'guests', 'users'], true) ? $authScope : 'all';
            }
            unset($row);

            return $rows;
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('rc_get_campaign_full')) {
    function rc_get_campaign_full(PDO $pdo, int $campaignId, ?int $userId = null): ?array
    {
        if ($campaignId > 0) {
            try {
                reclama_sync_campaign_status($pdo, $campaignId);
            } catch (Throwable $e) {
            }
        }

        $row = reclama_get_campaign($pdo, $campaignId);
        if (!$row) return null;
        if ($userId !== null && (int)($row['user_id'] ?? 0) !== $userId) return null;

        $row = array_merge($row, rc_get_targeting($pdo, $campaignId));

        if (!empty($row['source_post_id']) && rc_table_exists($pdo, 'posts')) {
            try {
                $st = $pdo->prepare('SELECT body, created_at FROM posts WHERE id = ? LIMIT 1');
                $st->execute([(int)$row['source_post_id']]);
                $post = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                $row['source_post_body']       = $post['body'] ?? null;
                $row['source_post_created_at'] = $post['created_at'] ?? null;
            } catch (Throwable $e) {
                $row['source_post_body'] = null;
                $row['source_post_created_at'] = null;
            }
        }

        $row['impressions_total']    = 0;
        $row['clicks_total']         = 0;
        $row['unique_viewers_total'] = 0;
        $row['ctr']                  = 0.0;

        if (function_exists('reclama_campaign_daily_series')) {
            $series = reclama_campaign_daily_series(
                $pdo,
                $campaignId,
                (string)($row['start_date'] ?? ''),
                (string)($row['end_date'] ?? ''),
                max(1, (int)($row['days_count'] ?? 30))
            );

            $totals = (array)($series['totals'] ?? []);
            $row['stats_range_start_date'] = (string)($series['start_date'] ?? '');
            $row['stats_range_end_date']   = (string)($series['end_date'] ?? '');
            $row['daily_stats']            = (array)($series['days'] ?? []);
            $row['impressions_total']      = (int)($totals['impressions_total'] ?? 0);
            $row['clicks_total']           = (int)($totals['clicks_total'] ?? 0);
            $row['unique_viewers_total']   = (int)($totals['unique_viewers_total'] ?? 0);
            $row['ctr']                    = (float)($totals['ctr'] ?? 0.0);
            return $row;
        }

        if (rc_table_exists($pdo, 'reclama_campaign_daily_stats')) {
            $cols = rc_daily_stats_columns($pdo);
            $impCol = $cols['impressions'] ?? null;
            $clkCol = $cols['clicks'] ?? null;
            $uniCol = $cols['unique'] ?? null;

            if ($impCol !== null || $clkCol !== null || $uniCol !== null) {
                $impExpr = $impCol !== null ? 'COALESCE(SUM(`' . $impCol . '`),0)' : '0';
                $clkExpr = $clkCol !== null ? 'COALESCE(SUM(`' . $clkCol . '`),0)' : '0';
                $uniExpr = $uniCol !== null ? 'COALESCE(SUM(`' . $uniCol . '`),0)' : '0';

                try {
                    $st = $pdo->prepare('
                        SELECT
                            ' . $impExpr . ' AS imp,
                            ' . $clkExpr . ' AS clk,
                            ' . $uniExpr . ' AS uni
                        FROM reclama_campaign_daily_stats
                        WHERE campaign_id = ?
                    ');
                    $st->execute([$campaignId]);
                    $stat = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    $row['impressions_total']    = (int)($stat['imp'] ?? 0);
                    $row['clicks_total']         = (int)($stat['clk'] ?? 0);
                    $row['unique_viewers_total'] = (int)($stat['uni'] ?? 0);
                } catch (Throwable $e) {
                }
            }
        } else {
            if (rc_table_exists($pdo, 'reclama_campaign_impressions')) {
                $row['impressions_total'] = reclama_safe_int($pdo, 'SELECT COUNT(*) FROM reclama_campaign_impressions WHERE campaign_id = ?', [$campaignId]);

                try {
                    $st = $pdo->prepare('
                        SELECT COUNT(DISTINCT CASE
                            WHEN viewer_user_id IS NOT NULL THEN CONCAT("u:", viewer_user_id)
                            WHEN viewer_session_key IS NOT NULL AND viewer_session_key <> "" THEN CONCAT("s:", viewer_session_key)
                            ELSE NULL
                        END)
                        FROM reclama_campaign_impressions
                        WHERE campaign_id = ?
                    ');
                    $st->execute([$campaignId]);
                    $row['unique_viewers_total'] = (int)$st->fetchColumn();
                } catch (Throwable $e) {
                    $row['unique_viewers_total'] = 0;
                }
            }
            if (rc_table_exists($pdo, 'reclama_campaign_clicks')) {
                $row['clicks_total'] = reclama_safe_int($pdo, 'SELECT COUNT(*) FROM reclama_campaign_clicks WHERE campaign_id = ?', [$campaignId]);
            }
        }

        $imp = (int)($row['impressions_total'] ?? 0);
        $clk = (int)($row['clicks_total'] ?? 0);
        $row['ctr'] = function_exists('reclama_calc_ctr')
            ? reclama_calc_ctr($imp, $clk)
            : ($imp > 0 ? round(($clk / $imp) * 100, 2) : 0.0);

        return $row;
    }
}

if (!function_exists('rc_user_top_campaigns')) {
    function rc_user_top_campaigns(PDO $pdo, int $userId, int $limit = 10, string $sort = 'impressions'): array
    {
        if (function_exists('reclama_user_top_campaigns')) {
            return reclama_user_top_campaigns($pdo, $userId, $limit, $sort);
        }
        return [];
    }
}

if (!function_exists('rc_user_posts')) {
    function rc_user_posts(PDO $pdo, int $userId, int $limit = 50): array
    {
        if ($userId <= 0 || !rc_table_exists($pdo, 'posts')) return [];

        $limit = max(1, min(200, $limit));
        try {
            $st = $pdo->prepare('SELECT id, body, created_at FROM posts WHERE user_id = ? ORDER BY id DESC LIMIT ' . (int)$limit);
            $st->execute([$userId]);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('rc_validate_url')) {
    function rc_validate_url(string $url, bool $allowBlank = false): ?string
    {
        $url = trim($url);
        if ($url === '') {
            return $allowBlank ? '' : null;
        }
        if (str_starts_with($url, '/')) {
            return mb_substr($url, 0, 255);
        }
        if (filter_var($url, FILTER_VALIDATE_URL) && preg_match('~^https?://~i', $url)) {
            return mb_substr($url, 0, 255);
        }
        return null;
    }
}

if (!function_exists('rc_username_safe')) {
    function rc_username_safe(string $username): string
    {
        $safe = preg_replace('~[^a-zA-Z0-9_\-]+~', '_', trim($username));
        return ($safe === null || $safe === '') ? 'user' : $safe;
    }
}

if (!function_exists('rc_upload_root')) {
    function rc_upload_root(): string
    {
        $doc = trim((string)($_SERVER['DOCUMENT_ROOT'] ?? ''));
        if ($doc !== '' && is_dir($doc)) {
            return rtrim($doc, '/');
        }
        return rtrim(dirname(__DIR__, 2), '/');
    }
}

if (!function_exists('rc_user_upload_dir')) {
    function rc_user_upload_dir(string $username): string
    {
        return '/upload/' . rc_username_safe($username) . '/reclama';
    }
}

if (!function_exists('rc_public_to_fs')) {
    function rc_public_to_fs(?string $publicPath): ?string
    {
        $publicPath = trim((string)$publicPath);
        if ($publicPath === '' || !str_starts_with($publicPath, '/upload/')) return null;
        return rc_upload_root() . $publicPath;
    }
}

if (!function_exists('rc_ensure_dir')) {
    function rc_ensure_dir(string $dir): bool
    {
        return is_dir($dir) || @mkdir($dir, 0775, true);
    }
}

if (!function_exists('rc_delete_public_file')) {
    function rc_delete_public_file(?string $publicPath): void
    {
        $fs = rc_public_to_fs($publicPath);
        if ($fs && is_file($fs)) {
            @unlink($fs);
        }
    }
}

if (!function_exists('rc_store_banner_upload')) {
    function rc_store_banner_upload(array $file, string $username, string $slot, int $campaignId = 0): array
    {
        $slot = ($slot === 'mobile') ? 'mobile' : 'desktop';
        $err  = (int)($file['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($err === UPLOAD_ERR_NO_FILE) {
            return ['ok' => false, 'reason' => 'no_file'];
        }
        if ($err !== UPLOAD_ERR_OK) {
            return ['ok' => false, 'reason' => 'upload_error'];
        }

        $tmp = (string)($file['tmp_name'] ?? '');
        if ($tmp === '' || !is_uploaded_file($tmp)) {
            return ['ok' => false, 'reason' => 'tmp_missing'];
        }

        $size = (int)($file['size'] ?? 0);
        if ($size <= 0 || $size > 8 * 1024 * 1024) {
            return ['ok' => false, 'reason' => 'bad_size'];
        }

        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = (string)$finfo->file($tmp);
        $extMap = [
            'image/jpeg' => 'jpg',
            'image/png'  => 'png',
            'image/webp' => 'webp',
            'image/gif'  => 'gif',
        ];
        $ext = $extMap[$mime] ?? null;
        if ($ext === null) {
            return ['ok' => false, 'reason' => 'bad_type'];
        }

        $basePublic = rc_user_upload_dir($username) . '/' . $slot;
        $baseFs     = rc_upload_root() . $basePublic;
        if (!rc_ensure_dir($baseFs)) {
            return ['ok' => false, 'reason' => 'mkdir_failed'];
        }

        try {
            $rand = bin2hex(random_bytes(6));
        } catch (Throwable $e) {
            $rand = substr(sha1((string)microtime(true) . '|' . mt_rand()), 0, 12);
        }

        $name = date('Ymd_His') . '_' . $rand;
        if ($campaignId > 0) {
            $name .= '_c' . $campaignId;
        }
        $name .= '.' . $ext;

        $fullFs = $baseFs . '/' . $name;
        if (!@move_uploaded_file($tmp, $fullFs)) {
            return ['ok' => false, 'reason' => 'move_failed'];
        }

        return [
            'ok'            => true,
            'public_path'   => $basePublic . '/' . $name,
            'original_name' => mb_substr((string)($file['name'] ?? ($slot . '.' . $ext)), 0, 255),
            'mime'          => $mime,
            'size'          => $size,
        ];
    }
}

if (!function_exists('rc_campaign_form_defaults')) {
    function rc_campaign_form_defaults(PDO $pdo): array
    {
        $today = date('Y-m-d');
        $days  = max(1, reclama_min_days($pdo));
        $end   = date('Y-m-d', strtotime('+' . max(0, $days - 1) . ' day'));

        return [
            'campaign_type'                => reclama_allow_post_campaigns($pdo) ? 'post' : 'banner',
            'title'                        => '',
            'source_post_id'               => '',
            'target_url'                   => '',
            'start_date'                   => $today,
            'end_date'                     => $end,
            'desktop_banner_path'          => '',
            'mobile_banner_path'           => '',
            'desktop_banner_original_name' => '',
            'mobile_banner_original_name'  => '',
            'country_code'                 => '',
            'exclude_countries'            => '',
            'city'                         => '',
            'gender'                       => 'any',
            'age_from'                     => '',
            'age_to'                       => '',
            'lang_code'                    => 'any',
            'device_type'                  => 'all',
            'auth_scope'                   => 'all',
            'segment_codes'                => '',
        ];
    }
}

if (!function_exists('rc_collect_campaign_input')) {
    function rc_collect_campaign_input(array $src, array $base): array
    {
        $out = $base;
        foreach (array_keys($base) as $k) {
            if (array_key_exists($k, $src)) {
                $out[$k] = is_string($src[$k]) ? trim($src[$k]) : $src[$k];
            }
        }
        return $out;
    }
}

if (!function_exists('rc_save_campaign')) {
    function rc_save_campaign(PDO $pdo, array $me, array $input, array $files, bool $submit, ?array $existing = null): array
    {
        $userId   = (int)($me['id'] ?? 0);
        $username = (string)($me['username'] ?? 'user');

        if ($userId <= 0) {
            return ['ok' => false, 'errors' => [rc_t('Користувача не знайдено.', 'Пользователь не найден.', 'User not found.')]];
        }
        if (!rc_table_exists($pdo, 'reclama_campaigns')) {
            return ['ok' => false, 'errors' => [rc_t('Немає таблиці reclama_campaigns.', 'Нет таблицы reclama_campaigns.', 'The reclama_campaigns table is missing.')]];
        }

        $type = trim((string)($input['campaign_type'] ?? 'post'));
        if (!in_array($type, ['post', 'banner'], true)) {
            $type = 'post';
        }

        if ($type === 'post' && !reclama_allow_post_campaigns($pdo)) {
            return ['ok' => false, 'errors' => [rc_t('Просування постів вимкнено.', 'Продвижение постов отключено.', 'Post campaigns are disabled.')]];
        }
        if ($type === 'banner' && !reclama_allow_banner_campaigns($pdo)) {
            return ['ok' => false, 'errors' => [rc_t('Банерна реклама вимкнена.', 'Баннерная реклама отключена.', 'Banner campaigns are disabled.')]];
        }

        $title = mb_substr(trim((string)($input['title'] ?? '')), 0, 190);
        $sourcePostId = (int)($input['source_post_id'] ?? 0);
        $targetUrlRaw = trim((string)($input['target_url'] ?? ''));

        $dateCheck = reclama_validate_dates(
            (string)($input['start_date'] ?? ''),
            (string)($input['end_date'] ?? ''),
            reclama_min_days($pdo),
            reclama_max_days($pdo)
        );

        $targetingCheck = reclama_validate_targeting([
            'country_code'       => $input['country_code'] ?? '',
            'exclude_countries'  => $input['exclude_countries'] ?? ($input['exclude_countries_json'] ?? ''),
            'city'               => $input['city'] ?? '',
            'gender'             => $input['gender'] ?? 'any',
            'age_from'           => $input['age_from'] ?? '',
            'age_to'             => $input['age_to'] ?? '',
            'lang_code'          => $input['lang_code'] ?? 'any',
            'device_type'        => $input['device_type'] ?? 'all',
            'auth_scope'         => $input['auth_scope'] ?? 'all',
            'segment_codes'      => $input['segment_codes'] ?? ($input['segment_codes_json'] ?? ''),
        ]);

        $errors = [];
        if (!$dateCheck['ok']) {
            foreach ((array)$dateCheck['errors'] as $err) {
                $errors[] = match ($err) {
                    'empty_dates' => rc_t('Вкажи дати кампанії.', 'Укажи даты кампании.', 'Set campaign dates.'),
                    'bad_period'  => rc_t('Дата завершення не може бути раніше дати старту.', 'Дата завершения не может быть раньше даты старта.', 'End date cannot be earlier than start date.'),
                    'too_short'   => rc_t('Період реклами занадто короткий.', 'Период рекламы слишком короткий.', 'Campaign period is too short.'),
                    'too_long'    => rc_t('Період реклами занадто довгий.', 'Период рекламы слишком длинный.', 'Campaign period is too long.'),
                    default       => $err,
                };
            }
        }
        if (!$targetingCheck['ok']) {
            foreach ((array)$targetingCheck['errors'] as $err) {
                $errors[] = match ($err) {
                    'age_range'        => rc_t('Перевір вік у таргетингу.', 'Проверь возраст в таргетинге.', 'Check the age range in targeting.'),
                    'country_excluded' => rc_t('Країну показу не можна одночасно вказати і в виключеннях.', 'Страну показа нельзя одновременно указать и в исключениях.', 'The main country cannot be listed in excluded countries at the same time.'),
                    default            => rc_t('Перевір налаштування таргетингу.', 'Проверь настройки таргетинга.', 'Check targeting settings.'),
                };
            }
        }

        $daysCount   = (int)($dateCheck['days_count'] ?? 0);
        $pricePerDay = reclama_price_per_day($pdo);
        $totalPrice  = reclama_calc_total($daysCount, $pricePerDay);

        $targetUrl = rc_validate_url($targetUrlRaw, $type === 'post');
        if ($targetUrl === null) {
            $errors[] = rc_t('Посилання має починатися з https://, http:// або /.', 'Ссылка должна начинаться с https://, http:// или /.', 'URL must start with https://, http:// or /.');
        }

        if ($type === 'post') {
            if ($sourcePostId <= 0) {
                $errors[] = rc_t('Оберіть пост для просування.', 'Выберите пост для продвижения.', 'Choose a post to promote.');
            } elseif (rc_table_exists($pdo, 'posts')) {
                try {
                    $st = $pdo->prepare('SELECT body FROM posts WHERE id = ? AND user_id = ? LIMIT 1');
                    $st->execute([$sourcePostId, $userId]);
                    $post = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                    if (!$post) {
                        $errors[] = rc_t('Обраний пост не знайдено.', 'Выбранный пост не найден.', 'The selected post was not found.');
                    }
                    if ($title === '' && $post) {
                        $title = rc_excerpt((string)($post['body'] ?? ''), 90);
                    }
                } catch (Throwable $e) {
                    $errors[] = rc_t('Не вдалося перевірити пост.', 'Не удалось проверить пост.', 'Failed to validate the post.');
                }
            }
        } else {
            if ($title === '') {
                $errors[] = rc_t('Вкажи назву кампанії.', 'Укажи название кампании.', 'Enter a campaign title.');
            }
            if ($targetUrl === '') {
                $errors[] = rc_t('Для банера потрібне посилання.', 'Для баннера нужна ссылка.', 'A banner campaign requires a target URL.');
            }
        }

        $desktopPath = trim((string)($existing['desktop_banner_path'] ?? ''));
        $mobilePath  = trim((string)($existing['mobile_banner_path'] ?? ''));
        $desktopOriginal = trim((string)($existing['desktop_banner_original_name'] ?? ''));
        $mobileOriginal  = trim((string)($existing['mobile_banner_original_name'] ?? ''));

        $desktopUpload = ['ok' => false, 'reason' => 'no_file'];
        $mobileUpload  = ['ok' => false, 'reason' => 'no_file'];

        if (isset($files['desktop_banner']) && is_array($files['desktop_banner'])) {
            $desktopUpload = rc_store_banner_upload($files['desktop_banner'], $username, 'desktop', (int)($existing['id'] ?? 0));
            if (($desktopUpload['ok'] ?? false) === true) {
                $desktopPath     = (string)$desktopUpload['public_path'];
                $desktopOriginal = (string)$desktopUpload['original_name'];
            } elseif (($desktopUpload['reason'] ?? '') !== 'no_file') {
                $errors[] = match ($desktopUpload['reason']) {
                    'bad_size'     => rc_t('Desktop-банер занадто великий (до 8 MB).', 'Desktop-баннер слишком большой (до 8 MB).', 'Desktop banner is too large (max 8 MB).'),
                    'bad_type'     => rc_t('Desktop-банер має бути JPG/PNG/WEBP/GIF.', 'Desktop-баннер должен быть JPG/PNG/WEBP/GIF.', 'Desktop banner must be JPG/PNG/WEBP/GIF.'),
                    'mkdir_failed' => rc_t('Не вдалося створити папку для desktop-банера.', 'Не удалось создать папку для desktop-баннера.', 'Failed to create a folder for the desktop banner.'),
                    default        => rc_t('Не вдалося завантажити desktop-банер.', 'Не удалось загрузить desktop-баннер.', 'Failed to upload the desktop banner.'),
                };
            }
        }

        if (isset($files['mobile_banner']) && is_array($files['mobile_banner'])) {
            $mobileUpload = rc_store_banner_upload($files['mobile_banner'], $username, 'mobile', (int)($existing['id'] ?? 0));
            if (($mobileUpload['ok'] ?? false) === true) {
                $mobilePath     = (string)$mobileUpload['public_path'];
                $mobileOriginal = (string)$mobileUpload['original_name'];
            } elseif (($mobileUpload['reason'] ?? '') !== 'no_file') {
                $errors[] = match ($mobileUpload['reason']) {
                    'bad_size'     => rc_t('Mobile-банер занадто великий (до 8 MB).', 'Mobile-баннер слишком большой (до 8 MB).', 'Mobile banner is too large (max 8 MB).'),
                    'bad_type'     => rc_t('Mobile-банер має бути JPG/PNG/WEBP/GIF.', 'Mobile-баннер должен быть JPG/PNG/WEBP/GIF.', 'Mobile banner must be JPG/PNG/WEBP/GIF.'),
                    'mkdir_failed' => rc_t('Не вдалося створити папку для mobile-банера.', 'Не удалось создать папку для mobile-баннера.', 'Failed to create a folder for the mobile banner.'),
                    default        => rc_t('Не вдалося завантажити mobile-банер.', 'Не удалось загрузить mobile-баннер.', 'Failed to upload the mobile banner.'),
                };
            }
        }

        if ($type === 'banner' && $desktopPath === '') {
            $errors[] = rc_t('Для банерної кампанії потрібен desktop-банер.', 'Для баннерной кампании нужен desktop-баннер.', 'A banner campaign requires a desktop banner.');
        }

        if ($errors) {
            if (($desktopUpload['ok'] ?? false) === true) rc_delete_public_file((string)$desktopUpload['public_path']);
            if (($mobileUpload['ok'] ?? false) === true) rc_delete_public_file((string)$mobileUpload['public_path']);
            return ['ok' => false, 'errors' => $errors];
        }

        $status = 'draft';
        $moderationStatus = 'pending';
        if ($submit) {
            if (reclama_require_moderation($pdo)) {
                $status = 'pending';
                $moderationStatus = 'pending';
            } else {
                $status = 'approved';
                $moderationStatus = 'approved';
            }
        }

        $campaignId = (int)($existing['id'] ?? 0);

        $pdo->beginTransaction();
        try {
            if ($campaignId > 0) {
                $st = $pdo->prepare('
                    UPDATE reclama_campaigns
                    SET
                        campaign_type = ?,
                        source_post_id = ?,
                        title = ?,
                        target_url = ?,
                        status = ?,
                        moderation_status = ?,
                        start_date = ?,
                        end_date = ?,
                        days_count = ?,
                        price_per_day = ?,
                        total_price = ?,
                        desktop_banner_path = ?,
                        mobile_banner_path = ?,
                        desktop_banner_original_name = ?,
                        mobile_banner_original_name = ?,
                        updated_at = NOW()
                    WHERE id = ? AND user_id = ?
                    LIMIT 1
                ');
                $st->execute([
                    $type,
                    $type === 'post' ? $sourcePostId : null,
                    $title,
                    $targetUrl !== '' ? $targetUrl : null,
                    $status,
                    $moderationStatus,
                    (string)$dateCheck['start_date'],
                    (string)$dateCheck['end_date'],
                    $daysCount,
                    $pricePerDay,
                    $totalPrice,
                    $desktopPath !== '' ? $desktopPath : null,
                    $mobilePath !== '' ? $mobilePath : null,
                    $desktopOriginal !== '' ? $desktopOriginal : null,
                    $mobileOriginal !== '' ? $mobileOriginal : null,
                    $campaignId,
                    $userId,
                ]);

                rc_save_targeting($pdo, $campaignId, (array)$targetingCheck['value']);
                reclama_log_moderation($pdo, $campaignId, 'edited', null, $submit ? 'Updated and submitted by user' : 'Updated by user');

                if ($submit) {
                    reclama_log_moderation($pdo, $campaignId, 'submitted', null, 'Submitted by user');
                }
            } else {
                $st = $pdo->prepare('
                    INSERT INTO reclama_campaigns
                        (user_id, campaign_type, source_post_id, title, target_url, status, moderation_status, payment_status,
                         start_date, end_date, days_count, price_per_day, total_price,
                         desktop_banner_path, mobile_banner_path, desktop_banner_original_name, mobile_banner_original_name,
                         created_at, updated_at)
                    VALUES
                        (?, ?, ?, ?, ?, ?, ?, "unpaid", ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
                ');
                $st->execute([
                    $userId,
                    $type,
                    $type === 'post' ? $sourcePostId : null,
                    $title,
                    $targetUrl !== '' ? $targetUrl : null,
                    $status,
                    $moderationStatus,
                    (string)$dateCheck['start_date'],
                    (string)$dateCheck['end_date'],
                    $daysCount,
                    $pricePerDay,
                    $totalPrice,
                    $desktopPath !== '' ? $desktopPath : null,
                    $mobilePath !== '' ? $mobilePath : null,
                    $desktopOriginal !== '' ? $desktopOriginal : null,
                    $mobileOriginal !== '' ? $mobileOriginal : null,
                ]);
                $campaignId = (int)$pdo->lastInsertId();

                rc_save_targeting($pdo, $campaignId, (array)$targetingCheck['value']);
                reclama_log_moderation($pdo, $campaignId, 'created', null, 'Created by user');

                if ($submit) {
                    reclama_log_moderation($pdo, $campaignId, 'submitted', null, 'Submitted by user');
                }
            }

            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            if (($desktopUpload['ok'] ?? false) === true) rc_delete_public_file((string)$desktopUpload['public_path']);
            if (($mobileUpload['ok'] ?? false) === true) rc_delete_public_file((string)$mobileUpload['public_path']);
            return ['ok' => false, 'errors' => [rc_t('Не вдалося зберегти кампанію.', 'Не удалось сохранить кампанию.', 'Failed to save the campaign.'), $e->getMessage()]];
        }

        if (($desktopUpload['ok'] ?? false) === true && !empty($existing['desktop_banner_path']) && $existing['desktop_banner_path'] !== $desktopPath) {
            rc_delete_public_file((string)$existing['desktop_banner_path']);
        }
        if (($mobileUpload['ok'] ?? false) === true && !empty($existing['mobile_banner_path']) && $existing['mobile_banner_path'] !== $mobilePath) {
            rc_delete_public_file((string)$existing['mobile_banner_path']);
        }

        return [
            'ok' => true,
            'campaign_id' => $campaignId,
            'status' => $status,
            'moderation_status' => $moderationStatus,
            'days_count' => $daysCount,
            'total_price' => $totalPrice,
        ];
    }
}