<?php
declare(strict_types=1);

function leads_path(): string
{
    return DATA_DIR . '/leads.json';
}

function activity_path(): string
{
    return DATA_DIR . '/activity.json';
}

function cms_leads(): array
{
    return read_json_file(leads_path(), ['items' => []]);
}

function cms_save_leads(array $data): bool
{
    backup_file(leads_path(), 'leads');
    return write_json_file(leads_path(), $data);
}

function detect_country_info(): array
{
    $map = [
        'RU' => 'Россия', 'BY' => 'Беларусь', 'KZ' => 'Казахстан', 'UA' => 'Украина',
        'DE' => 'Германия', 'US' => 'США', 'GB' => 'Великобритания', 'FR' => 'Франция',
        'IT' => 'Италия', 'ES' => 'Испания', 'NL' => 'Нидерланды', 'PL' => 'Польша',
        'TR' => 'Турция', 'AE' => 'ОАЭ', 'CN' => 'Китай', 'JP' => 'Япония',
    ];
    $code = strtoupper(trim((string)($_SERVER['HTTP_CF_IPCOUNTRY'] ?? $_SERVER['GEOIP_COUNTRY_CODE'] ?? $_SERVER['HTTP_X_COUNTRY_CODE'] ?? '')));
    if ($code !== '') {
        return ['code' => $code, 'name' => $map[$code] ?? $code];
    }
    return ['code' => '', 'name' => '—'];
}

function country_flag_emoji(string $code): string
{
    $code = strtoupper(trim($code));
    if (!preg_match('/^[A-Z]{2}$/', $code)) {
        return '🌐';
    }
    $base = 127397;
    return mb_chr($base + ord($code[0]), 'UTF-8') . mb_chr($base + ord($code[1]), 'UTF-8');
}

function current_visitor_key(): string
{
    if (session_status() === PHP_SESSION_ACTIVE && !empty(session_id())) {
        return session_id();
    }
    return sha1(client_ip() . '|' . ($_SERVER['HTTP_USER_AGENT'] ?? ''));
}

function activity_settings(): array
{
    $settings = cms_settings();
    $activity = $settings['activity'] ?? [];
    return [
        'auto_cleanup' => array_key_exists('auto_cleanup', $activity) ? !empty($activity['auto_cleanup']) : true,
        'retention_days' => max(1, (int)($activity['retention_days'] ?? 30)),
    ];
}

function save_activity_settings(array $activitySettings): void
{
    $settings = cms_settings();
    $settings['activity'] = [
        'auto_cleanup' => !empty($activitySettings['auto_cleanup']),
        'retention_days' => max(1, (int)($activitySettings['retention_days'] ?? 30)),
    ];
    cms_save_settings($settings);
}

function cleanup_activity(array $activity): array
{
    $cfg = activity_settings();
    $now = time();
    $retentionDays = !empty($cfg['auto_cleanup']) ? max(1, (int)$cfg['retention_days']) : 365;
    $keepSince = $now - ($retentionDays * 86400);
    $sessions = [];
    foreach (($activity['sessions'] ?? []) as $key => $session) {
        if ((int)($session['last_seen'] ?? 0) >= $keepSince) {
            $sessions[$key] = $session;
        }
    }
    $actions = array_values(array_filter(($activity['actions'] ?? []), static fn($row) => (int)($row['time'] ?? 0) >= $keepSince));
    return ['sessions' => $sessions, 'actions' => array_slice($actions, -20000)];
}

function persist_cleaned_activity(): array
{
    static $cache = null;
    if ($cache === null) {
        $cache = cleanup_activity(read_json_file(activity_path(), ['sessions' => [], 'actions' => []]));
        write_json_file(activity_path(), $cache);
    }
    return $cache;
}

function activity_write(array $activity): void
{
    write_json_file(activity_path(), $activity);
}

function activity_session_logs_dir(): string
{
    return DATA_DIR . '/crm/session-actions';
}

function activity_session_log_path(string $sessionKey): string
{
    $safe = preg_replace('/[^a-zA-Z0-9_-]/', '_', $sessionKey) ?: 'session';
    return activity_session_logs_dir() . '/' . $safe . '.json';
}

function activity_session_log_append(string $sessionKey, array $actionRow): void
{
    $path = activity_session_log_path($sessionKey);
    $data = read_json_file($path, ['actions' => []]);
    $data['actions'][] = $actionRow;
    $data['actions'] = array_slice($data['actions'], -300);
    write_json_file($path, $data);
}

function activity_session_log_read(string $sessionKey, int $limit = 120): array
{
    $path = activity_session_log_path($sessionKey);
    $data = read_json_file($path, ['actions' => []]);
    $rows = is_array($data['actions'] ?? null) ? $data['actions'] : [];
    return array_slice(array_reverse($rows), 0, $limit);
}

function clear_activity_logs(): void
{
    write_json_file(activity_path(), ['sessions' => [], 'actions' => []]);
    $dir = activity_session_logs_dir();
    if (is_dir($dir)) {
        foreach (glob($dir . '/*.json') ?: [] as $file) {
            @unlink($file);
        }
    }
}

function tracking_query_value(string $key): string
{
    $value = $_GET[$key] ?? $_POST[$key] ?? '';
    return is_string($value) ? trim($value) : '';
}

function tracking_referrer_host(string $referrer): string
{
    $host = parse_url($referrer, PHP_URL_HOST);
    return is_string($host) && $host !== '' ? $host : '—';
}

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

function geoip_cache_path(): string
{
    return DATA_DIR . '/crm/geoip-cache.json';
}

function is_public_ip_for_geo(string $ip): bool
{
    if ($ip === '' || in_array($ip, ['127.0.0.1', '0.0.0.0', '::1'], true)) {
        return false;
    }
    return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
}

function geoip_cache_read(): array
{
    return read_json_file(geoip_cache_path(), ['items' => []]);
}

function geoip_cache_write(array $data): void
{
    write_json_file(geoip_cache_path(), $data);
}

function geoip_country_name(string $code, string $fallback = '—'): string
{
    static $map = [
        'RU' => 'Россия','BY' => 'Беларусь','KZ' => 'Казахстан','UA' => 'Украина','DE' => 'Германия','US' => 'США','GB' => 'Великобритания',
        'FR' => 'Франция','IT' => 'Италия','ES' => 'Испания','NL' => 'Нидерланды','PL' => 'Польша','TR' => 'Турция','AE' => 'ОАЭ',
        'CN' => 'Китай','JP' => 'Япония','KR' => 'Южная Корея','CH' => 'Швейцария','AT' => 'Австрия','CZ' => 'Чехия',
        'PT' => 'Португалия','BE' => 'Бельгия','IL' => 'Израиль','CA' => 'Канада','AU' => 'Австралия','SE' => 'Швеция',
    ];
    $code = strtoupper(trim($code));
    return $map[$code] ?? (($fallback !== '') ? $fallback : ($code !== '' ? $code : '—'));
}

function geoip_tooltip(array $data): string
{
    $parts = ['IP: ' . ($data['ip'] ?? '—')];
    $country = trim((string)($data['country'] ?? ''));
    $countryCode = trim((string)($data['country_code'] ?? ''));
    if ($country !== '' || $countryCode !== '') {
        $parts[] = 'Страна: ' . ($country !== '' ? $country : '—') . ($countryCode !== '' ? ' (' . $countryCode . ')' : '');
    }
    foreach ([
        'region' => 'Регион',
        'city' => 'Город',
        'timezone' => 'Часовой пояс',
        'isp' => 'Провайдер',
        'org' => 'Организация',
        'asn' => 'ASN',
    ] as $key => $label) {
        if (!empty($data[$key])) {
            $parts[] = $label . ': ' . $data[$key];
        }
    }
    if (!empty($data['latitude']) && !empty($data['longitude'])) {
        $parts[] = 'Координаты: ' . $data['latitude'] . ', ' . $data['longitude'];
    }
    if (!empty($data['provider'])) {
        $parts[] = 'Источник: ' . $data['provider'];
    }
    return implode("\n", $parts);
}

function geoip_http_get(string $url, int $timeout = 2): ?string
{
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CONNECTTIMEOUT => $timeout,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTPHEADER => ['Accept: application/json', 'User-Agent: AuraGeo/2.0'],
        ]);
        $body = curl_exec($ch);
        $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return is_string($body) && $code >= 200 && $code < 400 ? $body : null;
    }
    $ctx = stream_context_create([
        'http' => [
            'timeout' => $timeout,
            'ignore_errors' => true,
            'header' => "Accept: application/json\r\nUser-Agent: AuraGeo/2.0\r\n",
        ],
        'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
    ]);
    $body = @file_get_contents($url, false, $ctx);
    return is_string($body) && $body !== '' ? $body : null;
}

function geoip_normalize(string $ip, array $raw, string $provider): ?array
{
    $countryCode = strtoupper(trim((string)($raw['country_code'] ?? $raw['countryCode'] ?? $raw['country'] ?? '')));
    $country = trim((string)($raw['country_name'] ?? $raw['country'] ?? $raw['countryName'] ?? ''));
    if ($countryCode === '' && preg_match('/^[A-Z]{2}$/', $country)) {
        $countryCode = $country;
        $country = geoip_country_name($countryCode, '');
    }
    if ($country === '' && $countryCode !== '') {
        $country = geoip_country_name($countryCode, '—');
    }
    $region = trim((string)($raw['region'] ?? $raw['regionName'] ?? $raw['region_name'] ?? ''));
    $city = trim((string)($raw['city'] ?? ''));
    $timezone = trim((string)($raw['timezone'] ?? $raw['time_zone'] ?? ''));
    $isp = trim((string)($raw['isp'] ?? $raw['connection']['isp'] ?? ''));
    $org = trim((string)($raw['org'] ?? $raw['organization'] ?? $raw['connection']['org'] ?? ''));
    $asn = trim((string)($raw['asn'] ?? $raw['connection']['asn'] ?? ''));
    $lat = trim((string)($raw['latitude'] ?? $raw['lat'] ?? $raw['geoplugin_latitude'] ?? ''));
    $lon = trim((string)($raw['longitude'] ?? $raw['lon'] ?? $raw['geoplugin_longitude'] ?? ''));
    if ($country === '' && $countryCode === '' && $city === '' && $region === '') {
        return null;
    }
    $data = [
        'ip' => $ip,
        'success' => true,
        'country_code' => $countryCode,
        'country' => $country ?: '—',
        'region' => $region,
        'city' => $city,
        'latitude' => $lat,
        'longitude' => $lon,
        'timezone' => $timezone,
        'isp' => $isp,
        'org' => $org,
        'asn' => $asn,
        'flag' => country_flag_emoji($countryCode),
        'provider' => $provider,
    ];
    $data['tooltip'] = geoip_tooltip($data);
    return $data;
}

function geoip_header_guess(string $ip): ?array
{
    $info = detect_country_info();
    if ($info['code'] === '') {
        return null;
    }
    $data = [
        'ip' => $ip,
        'success' => true,
        'country_code' => $info['code'],
        'country' => $info['name'],
        'region' => trim((string)($_SERVER['HTTP_CF_REGION'] ?? '')),
        'city' => trim((string)($_SERVER['HTTP_CF_IPCITY'] ?? $_SERVER['GEOIP_CITY'] ?? '')),
        'latitude' => '',
        'longitude' => '',
        'timezone' => trim((string)($_SERVER['HTTP_CF_TIMEZONE'] ?? '')),
        'isp' => '',
        'org' => '',
        'asn' => trim((string)($_SERVER['HTTP_CF_ASN'] ?? '')),
        'flag' => country_flag_emoji($info['code']),
        'provider' => 'server_headers',
    ];
    $data['tooltip'] = geoip_tooltip($data);
    return $data;
}

function geoip_provider_resolve(string $ip): ?array
{
    $providers = [
        'ipwho.is' => static function (string $ip): ?array {
            $body = geoip_http_get('https://ipwho.is/' . rawurlencode($ip), 2);
            if (!$body) return null;
            $json = json_decode($body, true);
            if (!is_array($json) || empty($json['success'])) return null;
            return geoip_normalize($ip, $json, 'ipwho.is');
        },
        'ipapi.co' => static function (string $ip): ?array {
            $body = geoip_http_get('https://ipapi.co/' . rawurlencode($ip) . '/json/', 2);
            if (!$body) return null;
            $json = json_decode($body, true);
            if (!is_array($json) || !empty($json['error'])) return null;
            return geoip_normalize($ip, $json, 'ipapi.co');
        },
        'ip-api.com' => static function (string $ip): ?array {
            $body = geoip_http_get('http://ip-api.com/json/' . rawurlencode($ip) . '?fields=status,country,countryCode,regionName,city,lat,lon,timezone,isp,org,as', 2);
            if (!$body) return null;
            $json = json_decode($body, true);
            if (!is_array($json) || (($json['status'] ?? '') !== 'success')) return null;
            $json['asn'] = (string)($json['as'] ?? '');
            return geoip_normalize($ip, $json, 'ip-api.com');
        },
        'geoplugin.net' => static function (string $ip): ?array {
            $body = geoip_http_get('https://www.geoplugin.net/json.gp?ip=' . rawurlencode($ip), 2);
            if (!$body) return null;
            $json = json_decode($body, true);
            if (!is_array($json) || empty($json['geoplugin_countryCode'])) return null;
            $json['country_code'] = (string)$json['geoplugin_countryCode'];
            $json['country_name'] = (string)($json['geoplugin_countryName'] ?? '');
            $json['region'] = (string)($json['geoplugin_regionName'] ?? '');
            $json['city'] = (string)($json['geoplugin_city'] ?? '');
            $json['timezone'] = (string)($json['geoplugin_timezone'] ?? '');
            return geoip_normalize($ip, $json, 'geoplugin.net');
        },
    ];
    foreach ($providers as $resolver) {
        try {
            $result = $resolver($ip);
            if ($result !== null) {
                return $result;
            }
        } catch (Throwable $e) {
            continue;
        }
    }
    return null;
}

function geoip_lookup(string $ip, bool $allowRemote = true): array
{
    $fallback = [
        'ip' => $ip,
        'success' => false,
        'country_code' => '',
        'country' => '—',
        'region' => '',
        'city' => '',
        'latitude' => '',
        'longitude' => '',
        'timezone' => '',
        'isp' => '',
        'org' => '',
        'asn' => '',
        'flag' => '🌐',
        'provider' => '',
        'tooltip' => 'Данные по IP не определены',
    ];
    if (!is_public_ip_for_geo($ip)) {
        $fallback['tooltip'] = 'Локальный или служебный IP';
        return $fallback;
    }

    $cache = geoip_cache_read();
    $items = $cache['items'] ?? [];
    $cached = $items[$ip] ?? null;
    if (is_array($cached) && (int)($cached['cached_at'] ?? 0) > time() - 86400 * 30) {
        return is_array($cached['data'] ?? null) ? $cached['data'] : $fallback;
    }

    $headerData = geoip_header_guess($ip);
    if ($headerData !== null && !empty($headerData['country_code'])) {
        $items[$ip] = ['cached_at' => time(), 'data' => $headerData];
        $cache['items'] = $items;
        geoip_cache_write($cache);
        return $headerData;
    }

    if (!$allowRemote) {
        return $fallback;
    }

    $data = geoip_provider_resolve($ip) ?? $fallback;
    $items[$ip] = ['cached_at' => time(), 'data' => $data];
    $cache['items'] = $items;
    geoip_cache_write($cache);
    return $data;
}

function enrich_session_geo(array $session, bool $allowRemote = false): array
{
    $ip = (string)($session['ip'] ?? '');
    $geo = geoip_lookup($ip, $allowRemote);
    if (!empty($geo['success'])) {
        $session['country'] = $geo['country'] ?: ($session['country'] ?? '—');
        $session['country_code'] = $geo['country_code'] ?: ($session['country_code'] ?? '');
        $session['country_flag'] = $geo['flag'] ?: ($session['country_flag'] ?? '🌐');
        $session['geo_city'] = $geo['city'] ?? '';
        $session['geo_region'] = $geo['region'] ?? '';
        $session['geo_timezone'] = $geo['timezone'] ?? '';
        $session['geo_isp'] = $geo['isp'] ?? '';
        $session['geo_org'] = $geo['org'] ?? '';
        $session['geo_asn'] = $geo['asn'] ?? '';
        $session['geo_latitude'] = $geo['latitude'] ?? '';
        $session['geo_longitude'] = $geo['longitude'] ?? '';
        $session['geo_provider'] = $geo['provider'] ?? '';
        $session['geo_tooltip'] = $geo['tooltip'] ?? '';
    } else {
        $session['country_flag'] = $session['country_flag'] ?? '🌐';
        $session['geo_tooltip'] = $geo['tooltip'] ?? 'Данные по IP не определены';
    }
    return $session;
}

function activity_score(array $session): array
{
    $hits = (int)($session['hits'] ?? 0);
    $score = min(100, $hits * 6);
    if (!empty($session['utm_source'])) $score += 8;
    if (!empty($session['referrer']) && ($session['referrer_host'] ?? '—') !== '—') $score += 5;
    if (in_array((string)($session['last_action'] ?? ''), ['booking_submit', 'lead_submit', 'booking_success'], true)) $score += 30;
    if ($hits >= 8) $score += 10;
    if ($hits >= 15) $score += 12;
    $score = min(100, $score);
    if ($score >= 75) return ['score' => $score, 'label' => 'Горячий', 'tone' => 'hot'];
    if ($score >= 45) return ['score' => $score, 'label' => 'Тёплый', 'tone' => 'warm'];
    return ['score' => $score, 'label' => 'Холодный', 'tone' => 'cold'];
}

function track_activity(string $action = 'view', array $extra = []): void
{
    $activity = persist_cleaned_activity();
    $key = current_visitor_key();
    $now = time();
    $referrer = (string)($extra['referrer'] ?? $_SERVER['HTTP_REFERER'] ?? '');
    $landing = (string)($extra['landing_url'] ?? tracking_landing_url());
    $country = detect_country_info();
    $session = $activity['sessions'][$key] ?? [
        'key' => $key,
        'ip' => client_ip(),
        'country' => $country['name'],
        'country_code' => $country['code'],
        'country_flag' => country_flag_emoji($country['code']),
        'user_agent' => (string)($_SERVER['HTTP_USER_AGENT'] ?? ''),
        'started_at' => $now,
        'last_seen' => $now,
        'hits' => 0,
        'current_page' => '',
        'current_url' => $landing,
        'last_action' => '',
        'referrer' => $referrer,
        'referrer_host' => tracking_referrer_host($referrer),
        'landing_url' => $landing,
        'utm_source' => tracking_query_value('utm_source'),
        'utm_medium' => tracking_query_value('utm_medium'),
        'utm_campaign' => tracking_query_value('utm_campaign'),
        'utm_term' => tracking_query_value('utm_term'),
        'utm_content' => tracking_query_value('utm_content'),
        'gclid' => tracking_query_value('gclid'),
        'yclid' => tracking_query_value('yclid'),
        'fbclid' => tracking_query_value('fbclid'),
    ];
    $session['last_seen'] = $now;
    $session['hits'] = (int)($session['hits'] ?? 0) + 1;
    if (isset($extra['page'])) {
        $session['current_page'] = (string)$extra['page'];
    }
    $session['current_url'] = (string)($extra['current_url'] ?? tracking_landing_url());
    $session['last_action'] = $action;
    if ($referrer !== '') {
        $session['referrer'] = $referrer;
        $session['referrer_host'] = tracking_referrer_host($referrer);
    }
    if (!empty($country['name']) && ($session['country'] ?? '—') === '—') {
        $session['country'] = $country['name'];
    }
    if (!empty($country['code']) && empty($session['country_code'])) {
        $session['country_code'] = $country['code'];
        $session['country_flag'] = country_flag_emoji($country['code']);
    }
    foreach (['utm_source','utm_medium','utm_campaign','utm_term','utm_content','gclid','yclid','fbclid'] as $keyName) {
        if (!empty($extra[$keyName])) {
            $session[$keyName] = (string)$extra[$keyName];
        } elseif (empty($session[$keyName])) {
            $session[$keyName] = tracking_query_value($keyName);
        }
    }
    $leadScore = activity_score($session);
    $session['lead_score'] = $leadScore['score'];
    $session['lead_temperature'] = $leadScore['label'];
    $session['lead_tone'] = $leadScore['tone'];

    $activity['sessions'][$key] = $session;
    $actionRow = [
        'time' => $now,
        'session' => $key,
        'ip' => $session['ip'],
        'country' => $session['country'],
        'country_code' => $session['country_code'] ?? '',
        'country_flag' => $session['country_flag'] ?? '🌐',
        'page' => (string)($extra['page'] ?? $session['current_page'] ?? ''),
        'current_url' => $session['current_url'],
        'action' => $action,
        'details' => (string)($extra['details'] ?? ''),
        'referrer' => $session['referrer'] ?? '',
        'referrer_host' => $session['referrer_host'] ?? '—',
        'utm_source' => $session['utm_source'] ?? '',
        'utm_medium' => $session['utm_medium'] ?? '',
        'utm_campaign' => $session['utm_campaign'] ?? '',
        'gclid' => $session['gclid'] ?? '',
        'yclid' => $session['yclid'] ?? '',
    ];
    $activity['actions'][] = $actionRow;
    $activity['actions'] = array_slice($activity['actions'], -20000);
    activity_write($activity);
    activity_session_log_append($key, $actionRow);
}

function activity_summary(): array
{
    $activity = persist_cleaned_activity();
    $actions = $activity['actions'] ?? [];
    $days = [];
    $hours = array_fill(0, 24, 0);
    foreach ($actions as $row) {
        $ts = (int)($row['time'] ?? 0);
        if ($ts <= 0) continue;
        $day = date('Y-m-d', $ts);
        $days[$day] = ($days[$day] ?? 0) + 1;
        $hours[(int)date('G', $ts)]++;
    }
    ksort($days);
    return ['days' => $days, 'hours' => $hours];
}

function activity_month_matrix(string $month): array
{
    if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
        $month = date('Y-m');
    }
    $activity = persist_cleaned_activity();
    $actions = $activity['actions'] ?? [];
    $counts = [];
    foreach ($actions as $row) {
        $day = date('Y-m-d', (int)($row['time'] ?? 0));
        if (strpos($day, $month . '-') === 0) {
            $counts[$day] = ($counts[$day] ?? 0) + 1;
        }
    }
    $first = strtotime($month . '-01');
    $daysInMonth = (int)date('t', $first);
    $weeks = [];
    $week = [];
    $firstWeekday = (int)date('N', $first);
    for ($i = 1; $i < $firstWeekday; $i++) {
        $week[] = null;
    }
    for ($d = 1; $d <= $daysInMonth; $d++) {
        $date = sprintf('%s-%02d', $month, $d);
        $week[] = ['date' => $date, 'day' => $d, 'count' => (int)($counts[$date] ?? 0)];
        if (count($week) === 7) {
            $weeks[] = $week;
            $week = [];
        }
    }
    if ($week) {
        while (count($week) < 7) $week[] = null;
        $weeks[] = $week;
    }
    return ['month' => $month, 'weeks' => $weeks, 'counts' => $counts];
}

function activity_day_breakdown(string $date): array
{
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
        $date = date('Y-m-d');
    }
    $activity = persist_cleaned_activity();
    $actions = array_values(array_filter($activity['actions'] ?? [], static fn(array $row): bool => date('Y-m-d', (int)($row['time'] ?? 0)) === $date));
    $hourly = array_fill(0, 24, 0);
    $countries = [];
    $refs = [];
    $ips = [];
    foreach ($actions as $row) {
        $hour = (int)date('G', (int)($row['time'] ?? 0));
        $hourly[$hour]++;
        $country = (string)($row['country'] ?? '—');
        $countries[$country] = ($countries[$country] ?? 0) + 1;
        $ref = (string)($row['referrer_host'] ?? '—');
        $refs[$ref] = ($refs[$ref] ?? 0) + 1;
        $ip = (string)($row['ip'] ?? '—');
        $ips[$ip] = ($ips[$ip] ?? 0) + 1;
    }
    arsort($countries); arsort($refs); arsort($ips);
    return [
        'date' => $date,
        'actions_count' => count($actions),
        'unique_ips' => count($ips),
        'hourly' => $hourly,
        'countries' => array_slice($countries, 0, 8, true),
        'referrers' => array_slice($refs, 0, 8, true),
        'ips' => array_slice($ips, 0, 12, true),
    ];
}

function activity_sessions(): array
{
    $activity = persist_cleaned_activity();
    $sessions = array_values($activity['sessions'] ?? []);
    foreach ($sessions as &$session) {
        $score = activity_score($session);
        $session['lead_score'] = $score['score'];
        $session['lead_temperature'] = $score['label'];
        $session['lead_tone'] = $score['tone'];
        $session['country_flag'] = $session['country_flag'] ?? country_flag_emoji((string)($session['country_code'] ?? ''));
    }
    unset($session);
    usort($sessions, static fn(array $a, array $b): int => (int)($b['last_seen'] ?? 0) <=> (int)($a['last_seen'] ?? 0));
    return $sessions;
}

function activity_enrich_sessions(array $sessions, int $remoteLimit = 8): array
{
    $usedRemote = 0;
    foreach ($sessions as &$session) {
        $allowRemote = $usedRemote < $remoteLimit;
        $before = (string)($session['country_code'] ?? '');
        $session = enrich_session_geo($session, $allowRemote);
        if ($allowRemote && $before === '' && !empty($session['country_code'])) {
            $usedRemote++;
        }
    }
    unset($session);
    return $sessions;
}

function activity_actions_for_session(string $sessionKey, int $limit = 100): array
{
    $rows = activity_session_log_read($sessionKey, $limit);
    if ($rows !== []) {
        return $rows;
    }
    $activity = persist_cleaned_activity();
    $rows = array_values(array_filter($activity['actions'] ?? [], static fn(array $row): bool => (string)($row['session'] ?? '') === $sessionKey));
    if ($rows !== []) {
        write_json_file(activity_session_log_path($sessionKey), ['actions' => array_slice($rows, -300)]);
    }
    return array_slice(array_reverse($rows), 0, $limit);
}

function online_visitors(int $seconds = 90): array
{
    $activity = persist_cleaned_activity();
    $now = time();
    $rows = array_values(array_filter(($activity['sessions'] ?? []), static fn($session) => (int)($session['last_seen'] ?? 0) >= $now - $seconds));
    foreach ($rows as &$row) {
        $score = activity_score($row);
        $row['lead_score'] = $score['score'];
        $row['lead_temperature'] = $score['label'];
        $row['lead_tone'] = $score['tone'];
        $row['country_flag'] = $row['country_flag'] ?? country_flag_emoji((string)($row['country_code'] ?? ''));
    }
    unset($row);
    return activity_enrich_sessions($rows, 6);
}

function visitor_visits_by_ip(string $ip): int
{
    $activity = persist_cleaned_activity();
    $count = 0;
    foreach (($activity['actions'] ?? []) as $row) {
        if (($row['ip'] ?? '') === $ip && ($row['action'] ?? '') === 'pageview') {
            $count++;
        }
    }
    return $count;
}

function recent_activity(int $limit = 50, ?string $date = null): array
{
    $activity = persist_cleaned_activity();
    $rows = $activity['actions'] ?? [];
    if ($date && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
        $rows = array_values(array_filter($rows, static fn(array $row): bool => date('Y-m-d', (int)($row['time'] ?? 0)) === $date));
    }
    return array_reverse(array_slice($rows, -$limit));
}

function create_lead(array $lead): void
{
    $data = cms_leads();
    $items = $data['items'] ?? [];
    $lead['id'] = $lead['id'] ?? ('lead_' . date('Ymd_His') . '_' . substr(bin2hex(random_bytes(3)), 0, 6));
    $lead['created_at'] = $lead['created_at'] ?? date('c');
    $lead['updated_at'] = date('c');
    array_unshift($items, $lead);
    $data['items'] = $items;
    cms_save_leads($data);
}
