<?php
function ugfx_read_json(string $path, $default = []) {
    if (!file_exists($path)) return $default;
    $data = json_decode(file_get_contents($path), true);
    return is_array($data) ? $data : $default;
}

function ugfx_write_json(string $path, $data): bool {
    $dir = dirname($path);
    if (!is_dir($dir)) mkdir($dir, 0775, true);
    return (bool) file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}

function ugfx_get_settings(): array {
    static $settings = null;
    if ($settings !== null) return $settings;
    $path = dirname(__DIR__) . '/data/settings.json';
    $settings = ugfx_read_json($path, []);
    return is_array($settings) ? $settings : [];
}

function ugfx_save_settings(array $settings): bool {
    $path = dirname(__DIR__) . '/data/settings.json';
    return ugfx_write_json($path, $settings);
}

function ugfx_chat_enabled(): bool {
    $settings = ugfx_get_settings();
    return !array_key_exists('chat_enabled', $settings) || !empty($settings['chat_enabled']);
}

function ugfx_chat_public_status(): array {
    if (ugfx_chat_enabled()) {
        return ['enabled' => true, 'message' => ''];
    }
    $settings = ugfx_get_settings();
    $message = trim((string)($settings['chat_disabled_message'] ?? 'Chat is currently turned off by the station team. Please check back later.'));
    return ['enabled' => false, 'message' => $message ?: 'Chat is currently turned off by the station team. Please check back later.'];
}

function ugfx_file_is_safe_upload(array $file, array $allowedMimeStarts = ['image/', 'audio/']): bool {
    $tmp = (string)($file['tmp_name'] ?? '');
    if ($tmp === '' || !is_file($tmp)) return false;
    if (!function_exists('finfo_open')) return true;
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    if (!$finfo) return true;
    $mime = (string)finfo_file($finfo, $tmp);
    finfo_close($finfo);
    foreach ($allowedMimeStarts as $allowed) {
        if (str_starts_with($mime, $allowed)) return true;
    }
    return false;
}

function ugfx_csrf_token(): string {
    if (empty($_SESSION['ugfx_csrf']) || !is_string($_SESSION['ugfx_csrf'])) {
        $_SESSION['ugfx_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['ugfx_csrf'];
}

function ugfx_verify_csrf(?string $token): bool {
    $sessionToken = (string)($_SESSION['ugfx_csrf'] ?? '');
    $token = trim((string)$token);
    return $sessionToken !== '' && $token !== '' && hash_equals($sessionToken, $token);
}

function ugfx_verify_csrf_or_die(): void {
    $token = (string)($_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');
    if (ugfx_verify_csrf($token)) return;
    http_response_code(419);
    echo '<div style="padding:24px;font-family:Inter,Arial,sans-serif;color:white;background:#111418;min-height:100vh">Security check failed. Please refresh the page and try again.</div>';
    exit;
}

function ugfx_admin_log(string $action, array $context = []): void {
    $path = dirname(__DIR__) . '/storage/logs/admin-actions.log';
    $dir = dirname($path);
    if (!is_dir($dir)) @mkdir($dir, 0775, true);
    $line = [
        'time' => date('c'),
        'user' => (string)((ugfx_admin_current_user()['username'] ?? 'guest')),
        'action' => $action,
        'ip' => (string)($_SERVER['REMOTE_ADDR'] ?? ''),
        'context' => $context,
    ];
    @file_put_contents($path, json_encode($line, JSON_UNESCAPED_SLASHES) . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function ugfx_protect_storage_dir(string $dir): void {
    if (!is_dir($dir)) @mkdir($dir, 0775, true);
    $index = rtrim($dir, '/\\') . '/index.html';
    if (!is_file($index)) @file_put_contents($index, '');
    $htaccess = rtrim($dir, '/\\') . '/.htaccess';
    if (!is_file($htaccess)) {
        $rules = <<<'HTACCESS'
Options -Indexes
<FilesMatch "\.(php|phtml|phar|cgi|pl|py|sh)$">
Deny from all
</FilesMatch>
HTACCESS;
        @file_put_contents($htaccess, $rules);
    }
}

function ugfx_cleanup_chat_uploads(int $maxAgeDays = 30): void {
    $dirs = [dirname(__DIR__) . '/storage/chat', dirname(__DIR__) . '/storage/uploads'];
    $cutoff = time() - max(1, $maxAgeDays) * 86400;
    foreach ($dirs as $dir) {
        if (!is_dir($dir)) continue;
        foreach (glob($dir . '/*') ?: [] as $file) {
            if (!is_file($file)) continue;
            $base = basename($file);
            if ($base === 'index.html' || $base === '.htaccess') continue;
            if (@filemtime($file) !== false && @filemtime($file) < $cutoff) {
                @unlink($file);
            }
        }
    }
}


function ugfx_admin_users(): array {
    global $ugfx;
    $users = ugfx_read_json($ugfx['storage']['users'] ?? '', []);
    return is_array($users) ? array_values(array_filter($users, 'is_array')) : [];
}

function ugfx_admin_current_user(): ?array {
    $user = $_SESSION['ugfx_admin'] ?? null;
    return is_array($user) ? $user : null;
}


function ugfx_admin_save_users(array $users): bool {
    global $ugfx;
    return ugfx_write_json($ugfx['storage']['users'] ?? '', array_values($users));
}

function ugfx_admin_update_user_meta(string $username, array $meta): void {
    $username = trim($username);
    if ($username === '') return;
    $items = ugfx_admin_users();
    $changed = false;
    foreach ($items as &$item) {
        if (strcasecmp((string)($item['username'] ?? ''), $username) !== 0) continue;
        foreach ($meta as $k => $v) {
            $item[$k] = $v;
        }
        $changed = true;
        break;
    }
    unset($item);
    if ($changed) ugfx_admin_save_users($items);
}

function ugfx_admin_touch_current_user(): void {
    $user = ugfx_admin_current_user();
    if (!$user || empty($user['username'])) return;
    $now = date('Y-m-d H:i:s');
    ugfx_admin_update_user_meta((string)$user['username'], ['last_seen' => $now]);
    $_SESSION['ugfx_admin']['last_seen'] = $now;
}

function ugfx_admin_online_users(int $seconds = 300): array {
    $threshold = time() - $seconds;
    return array_values(array_filter(ugfx_admin_users(), function($item) use ($threshold) {
        $seen = strtotime((string)($item['last_seen'] ?? ''));
        return $seen && $seen >= $threshold;
    }));
}

function ugfx_admin_logged_in(): bool {
    return ugfx_admin_current_user() !== null;
}

function ugfx_admin_attempt(string $username, string $password): bool {
    $username = trim($username);
    $password = (string)$password;
    if ($username === '' || $password === '') {
        return false;
    }

    foreach (ugfx_admin_users() as $user) {
        $storedUser = trim((string)($user['username'] ?? ''));
        if ($storedUser === '' || strcasecmp($storedUser, $username) !== 0) {
            continue;
        }

        $storedPassword = (string)($user['password'] ?? '');
        $valid = false;
        if ($storedPassword !== '') {
            if (password_get_info($storedPassword)['algo'] !== null) {
                $valid = password_verify($password, $storedPassword);
            } else {
                $valid = hash_equals($storedPassword, $password);
            }
        }

        if (!$valid) {
            return false;
        }

        $now = date('Y-m-d H:i:s');
        $_SESSION['ugfx_admin'] = [
            'username' => $storedUser,
            'role' => trim((string)($user['role'] ?? 'admin')) ?: 'admin',
            'permissions' => is_array($user['permissions'] ?? null) ? array_values($user['permissions']) : [],
            'temp_login' => !empty($user['temp_login']),
            'logged_in_at' => time(),
            'last_login' => $now,
            'last_seen' => $now,
        ];
        ugfx_admin_update_user_meta($storedUser, ['last_login' => $now, 'last_seen' => $now]);
        session_regenerate_id(true);
        return true;
    }

    return false;
}


function ugfx_normalize_role(string $role): string {
    $role = strtolower(trim($role));
    return $role !== '' ? $role : 'admin';
}

function ugfx_admin_role_rank(string $role): int {
    return match (ugfx_normalize_role($role)) {
        'superadmin' => 100,
        'admin' => 80,
        'editor' => 50,
        'dj' => 30,
        default => 10,
    };
}

function ugfx_admin_permission_defaults(string $role): array {
    $role = ugfx_normalize_role($role);
    return match ($role) {
        'superadmin' => ['dashboard','chat','news','events','pages','friends','djs','schedule','requests','contact','applications','users','media'],
        'admin' => ['dashboard','chat','news','events','pages','friends','djs','schedule','requests','contact','applications','media'],
        'editor' => ['dashboard','chat','news','events','pages','friends','djs','media'],
        'dj' => ['dashboard','chat'],
        default => ['dashboard'],
    };
}

function ugfx_admin_permissions_for_user(?array $user = null): array {
    $user = $user ?: ugfx_admin_current_user();
    if (!$user) return [];
    $permissions = $user['permissions'] ?? ugfx_admin_permission_defaults((string)($user['role'] ?? 'admin'));
    if (!is_array($permissions) || !$permissions) {
        $permissions = ugfx_admin_permission_defaults((string)($user['role'] ?? 'admin'));
    }
    $permissions = array_values(array_unique(array_filter(array_map(fn($v) => strtolower(trim((string)$v)), $permissions))));
    if (!in_array('dashboard', $permissions, true)) $permissions[] = 'dashboard';
    return $permissions;
}

function ugfx_admin_can(string $permission, ?array $user = null): bool {
    $user = $user ?: ugfx_admin_current_user();
    if (!$user) return false;
    if (ugfx_admin_role_rank((string)($user['role'] ?? '')) >= 100) return true;
    return in_array(strtolower($permission), ugfx_admin_permissions_for_user($user), true);
}

function ugfx_require_permission(string $permission): void {
    ugfx_require_admin();
    if (ugfx_admin_can($permission)) return;
    http_response_code(403);
    echo '<div style="padding:24px;font-family:Inter,Arial,sans-serif;color:white;background:#111418;min-height:100vh">Access denied for this admin section.</div>';
    exit;
}

function ugfx_admin_all_permissions(): array {
    return [
        'dashboard' => 'Dashboard',
        'chat' => 'Chat',
        'news' => 'News',
        'events' => 'Events',
        'pages' => 'Pages',
        'friends' => 'Friends',
        'djs' => 'DJs / Team',
        'schedule' => 'Schedule',
        'requests' => 'Requests',
        'contact' => 'Contact',
        'applications' => 'Applications',
        'users' => 'Users & Roles',
        'media' => 'Media uploads',
    ];
}

function ugfx_upload_file(array $file, array $allowedExts = ['jpg','jpeg','png','gif','webp'], int $maxBytes = 6291456): ?string {
    global $ugfx;
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return null;
    if (($file['size'] ?? 0) <= 0 || ($file['size'] ?? 0) > $maxBytes) return null;
    if (!ugfx_file_is_safe_upload($file)) return null;
    $ext = strtolower(pathinfo((string)($file['name'] ?? ''), PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExts, true)) return null;
    $dir = dirname($ugfx['storage']['chat_uploads']) . '/uploads';
    ugfx_protect_storage_dir($dir);
    $base = ugfx_slugify(pathinfo((string)($file['name'] ?? 'upload'), PATHINFO_FILENAME));
    $name = $base . '-' . date('YmdHis') . '-' . bin2hex(random_bytes(3)) . '.' . $ext;
    $target = $dir . '/' . $name;
    if (!move_uploaded_file($file['tmp_name'], $target)) return null;
    return 'storage/uploads/' . $name;
}

function ugfx_chat_ban_data(): array {
    global $ugfx;
    $data = ugfx_read_json($ugfx['storage']['chat_bans'] ?? '', ['banned' => [], 'muted' => []]);
    if (!is_array($data)) $data = ['banned' => [], 'muted' => []];
    $data['banned'] = array_values(array_unique(array_filter(array_map('trim', (array)($data['banned'] ?? [])))));
    $data['muted'] = array_values(array_unique(array_filter(array_map('trim', (array)($data['muted'] ?? [])))));
    return $data;
}

function ugfx_save_chat_ban_data(array $data): bool {
    global $ugfx;
    $clean = [
        'banned' => array_values(array_unique(array_filter(array_map('trim', (array)($data['banned'] ?? []))))),
        'muted' => array_values(array_unique(array_filter(array_map('trim', (array)($data['muted'] ?? []))))),
    ];
    return ugfx_write_json($ugfx['storage']['chat_bans'], $clean);
}

function ugfx_chat_name_state(string $name): string {
    $name = trim($name);
    if ($name === '') return 'ok';
    $data = ugfx_chat_ban_data();
    foreach ((array)$data['banned'] as $entry) if (strcasecmp((string)$entry, $name) === 0) return 'banned';
    foreach ((array)$data['muted'] as $entry) if (strcasecmp((string)$entry, $name) === 0) return 'muted';
    return 'ok';
}

function ugfx_require_admin(): void {
    if (ugfx_admin_logged_in()) {
        return;
    }
    if (!headers_sent()) {
        header('Location: login.php');
    }
    exit;
}

function ugfx_asset(string $path): string {
    return 'assets/' . ltrim($path, '/');
}

function ugfx_vendor_asset(string $localPath, string $cdnUrl, string $prefix = ''): string {
    $normalized = ltrim($localPath, '/');
    $absolute = dirname(__DIR__) . '/' . $normalized;
    if (file_exists($absolute)) {
        return $prefix . $normalized;
    }
    return $cdnUrl;
}

function ugfx_is_active(string $page, string $current): string {
    return $page === $current ? 'active' : '';
}

function ugfx_slugify(string $text): string {
    $text = strtolower(trim($text));
    $text = preg_replace('/[^a-z0-9]+/', '-', $text);
    return trim($text ?? '', '-') ?: 'page';
}

function ugfx_strlen(string $text): int {
    return function_exists('mb_strlen') ? mb_strlen($text) : strlen($text);
}

function ugfx_substr(string $text, int $start, ?int $length = null): string {
    if (function_exists('mb_substr')) {
        return $length === null ? mb_substr($text, $start) : mb_substr($text, $start, $length);
    }
    return $length === null ? substr($text, $start) : substr($text, $start, $length);
}

function ugfx_excerpt(string $text, int $length = 180): string {
    $text = trim(strip_tags($text));
    return ugfx_strlen($text) > $length ? ugfx_substr($text, 0, $length - 1) . '…' : $text;
}

function ugfx_sort_items_by_date_desc(array $items): array {
    usort($items, function ($a, $b) {
        return strtotime((string)($b['start'] ?? $b['date'] ?? '')) <=> strtotime((string)($a['start'] ?? $a['date'] ?? ''));
    });
    return $items;
}

function ugfx_get_news(int $limit = 6): array {
    global $ugfx;
    $items = ugfx_read_json($ugfx['storage']['news'], []);
    $items = array_values(array_filter($items, function ($item) { return ($item['published'] ?? 0) == 1; }));
    return array_slice($items, 0, $limit);
}

function ugfx_get_events(int $limit = 4): array {
    global $ugfx;
    $items = ugfx_read_json($ugfx['storage']['events'], []);
    $items = array_values(array_filter($items, function ($item) { return ($item['published'] ?? 0) == 1; }));
    $items = ugfx_sort_items_by_date_desc($items);
    return array_slice($items, 0, $limit);
}

function ugfx_get_djs(int $limit = 8): array {
    global $ugfx;
    $items = ugfx_read_json($ugfx['storage']['djs'], []);
    $items = array_values(array_filter($items, function ($item) {
        return !isset($item['active']) || !empty($item['active']);
    }));
    usort($items, function ($a, $b) {
        $sort = ((int)($a['sort_order'] ?? 9999)) <=> ((int)($b['sort_order'] ?? 9999));
        if ($sort !== 0) return $sort;
        return strcmp((string)($a['name'] ?? ''), (string)($b['name'] ?? ''));
    });
    return array_slice($items, 0, $limit);
}

function ugfx_get_top50(int $limit = 10): array {
    global $ugfx;
    $items = ugfx_read_json($ugfx['storage']['top50'], []);
    uasort($items, function ($a, $b) { return ($b['count'] ?? 0) <=> ($a['count'] ?? 0); });
    return array_slice($items, 0, $limit, true);
}

function ugfx_get_schedule(): array {
    global $ugfx;
    return ugfx_read_json($ugfx['storage']['schedule'], []);
}

function ugfx_get_friends(int $limit = 12): array {
    global $ugfx;
    return array_slice(ugfx_read_json($ugfx['storage']['friends'], []), 0, $limit);
}

function ugfx_get_pages(bool $publishedOnly = true): array {
    global $ugfx;
    $items = ugfx_read_json($ugfx['storage']['pages'], []);
    if ($publishedOnly) {
        $items = array_values(array_filter($items, function ($item) { return ($item['published'] ?? 0) == 1; }));
    }
    return $items;
}

function ugfx_get_page_by_slug(string $slug): ?array {
    foreach (ugfx_get_pages(false) as $page) {
        if (($page['slug'] ?? '') === $slug) {
            return $page;
        }
    }
    return null;
}

function ugfx_day_name(?int $timestamp = null): string {
    return date('l', $timestamp ?? time());
}

function ugfx_current_show(): array {
    $schedule = ugfx_get_schedule();
    $day = ugfx_day_name();
    $hour = (int) date('G');
    $current = $schedule[$day][$hour] ?? 'Underground FX Live';
    $next = $schedule[$day][($hour + 1) % 24] ?? 'Underground FX Live';
    return ['day' => $day, 'hour' => $hour, 'current' => $current, 'next' => $next];
}


function ugfx_merge_global_stats(array ...$sets): array {
    $merged = [
        'listeners' => 0,
        'listener_connections' => 0,
        'clients' => 0,
        'sources' => 0,
        'host' => '',
        'server_id' => '',
        'server_start' => '',
    ];
    foreach ($sets as $set) {
        if (!is_array($set)) continue;
        foreach (['listeners','listener_connections','clients','sources'] as $field) {
            if (isset($set[$field]) && (int)$set[$field] > (int)$merged[$field]) {
                $merged[$field] = (int)$set[$field];
            }
        }
        foreach (['host','server_id','server_start'] as $field) {
            if (($merged[$field] ?? '') === '' && trim((string)($set[$field] ?? '')) !== '') {
                $merged[$field] = trim((string)$set[$field]);
            }
        }
    }
    return $merged;
}

function ugfx_icecast_status(): array {
    static $cached = null;
    if ($cached !== null) {
        return $cached;
    }

    $cfg = ugfx_get_stream_config();
    $playerUrls = ugfx_get_player_urls();
    $cacheFile = __DIR__ . '/../data/icecast_stats_cache.json';
    $cacheTtl = 15;

    if (is_file($cacheFile) && (time() - (int)filemtime($cacheFile) <= $cacheTtl)) {
        $cachedData = json_decode((string)@file_get_contents($cacheFile), true);
        $cacheHasUsefulData = is_array($cachedData)
            && isset($cachedData['server_listeners'])
            && (
                (int)($cachedData['source_count'] ?? 0) > 0
                || !empty($cachedData['public_status_ok'])
                || !empty($cachedData['admin_status_ok'])
                || (int)($cachedData['server_listeners'] ?? 0) > 0
                || trim((string)($cachedData['title'] ?? '')) !== ''
            );
        $cacheLooksDead = $cacheHasUsefulData
            && (int)($cachedData['server_listeners'] ?? 0) <= 0
            && (int)($cachedData['current_listeners'] ?? 0) <= 0
            && trim((string)($cachedData['title'] ?? '')) !== '';
        if ($cacheHasUsefulData && !$cacheLooksDead) {
            return $cached = $cachedData;
        }
    }

    $show = ugfx_current_show();
    $fallback = [
        'ok' => false,
        'server_listeners' => 0,
        'current_listeners' => 0,
        'total_listener_connections' => 0,
        'title' => 'Underground FX Live',
        'artist' => $show['current'] ?? 'Underground FX Radio',
        'mount' => (string)($cfg['mount_auto'] ?? '/autodj'),
        'is_live' => false,
        'status' => 'LIVE',
        'stream_url' => $playerUrls['preferred'],
        'public_stream_url' => $playerUrls['public'],
        'direct_stream_url' => $playerUrls['direct'],
        'proxy_stream_url' => $playerUrls['proxy'],
    ];


    $publicXsl = ugfx_fetch_public_status_xsl();
    $parsedPublic = $publicXsl !== '' ? ugfx_parse_public_status_xsl($publicXsl) : ['global' => [], 'sources' => []];

    $needAdmin = false;
    if (empty($parsedPublic['sources'])) {
        $needAdmin = true;
    } else {
        $autoMount = (string)($cfg['mount_auto'] ?? '/autodj');
        $mainMount = (string)($cfg['mount_main'] ?? '/stream');
        $hasUseful = false;
        foreach (($parsedPublic['sources'] ?? []) as $src) {
            $mount = (string)($src['mount'] ?? '');
            $titleProbe = trim((string)($src['title'] ?? $src['x_icy_title'] ?? ''));
            if (($mount === $mainMount && (int)($src['listeners'] ?? 0) > 0) || ($mount === $autoMount && $titleProbe !== '')) {
                $hasUseful = true;
                break;
            }
        }
        $needAdmin = !$hasUseful;
    }

    $xml = '';
    $xsl = '';
    if ($needAdmin) {
        $xsl = ugfx_fetch_icecast_xsl();
        if ($xsl === '') {
            $xml = ugfx_fetch_icecast_xml();
        }
    }

    if ($xml === '' && $xsl === '' && $publicXsl === '') {
        if (is_file($cacheFile)) {
            $stale = json_decode((string)@file_get_contents($cacheFile), true);
            $staleIsUseful = is_array($stale)
                && (
                    (int)($stale['source_count'] ?? 0) > 0
                    || !empty($stale['public_status_ok'])
                    || !empty($stale['admin_status_ok'])
                    || (int)($stale['server_listeners'] ?? 0) > 0
                    || trim((string)($stale['title'] ?? '')) !== ''
                );
            if ($staleIsUseful) {
                $stale['ok'] = false;
                $stale['_warning'] = 'Icecast fetch failed (served stale cache)';
                return $cached = $stale;
            }
        }
        return $cached = $fallback;
    }

    $parsedXml = $xml !== '' ? ugfx_parse_icecast_data($xml) : ['global' => [], 'sources' => []];
    $parsedXsl = $xsl !== '' ? ugfx_parse_stats_xsl($xsl) : ['global' => [], 'sources' => []];

    $global = ugfx_merge_global_stats($parsedPublic['global'] ?? [], $parsedXsl['global'] ?? [], $parsedXml['global'] ?? []);
    $sources = ugfx_merge_icecast_sources(ugfx_merge_icecast_sources($parsedPublic['sources'] ?? [], $parsedXsl['sources'] ?? []), $parsedXml['sources'] ?? []);
    $active = ugfx_pick_preferred_source($sources) ?? [];

    $autoSource = null;
    foreach ($sources as $source) {
        if (($source['mount'] ?? '') === (string)$cfg['mount_auto']) {
            $autoSource = $source;
            break;
        }
    }

    $isLive = (($active['mount'] ?? '') === (string)$cfg['mount_main']) && (int)($active['listeners'] ?? 0) > 0;
    $rawTitle = trim((string)($active['title'] ?? $active['x_icy_title'] ?? ''));
    $artist = trim((string)($active['server_name'] ?? ''));

    if ($isLive) {
        $title = $rawTitle !== '' ? $rawTitle : 'Live DJ On Air';
        if ($artist === '') $artist = $show['current'] ?? 'Underground FX Live';
    } else {
        $fallbackTitle = trim((string)($autoSource['title'] ?? $autoSource['x_icy_title'] ?? ''));
        $title = $rawTitle !== '' ? $rawTitle : ($fallbackTitle !== '' ? $fallbackTitle : 'Underground FX Live');
        if ($artist === '') $artist = trim((string)($autoSource['server_name'] ?? 'FX Vibe Radio Flow'));
        if ($artist === '') $artist = 'FX Vibe Radio Flow';
    }

    $currentListeners = (int)($active['listeners'] ?? 0);
    $maxSourceListeners = 0;
    foreach ($sources as $sourceProbe) {
        $maxSourceListeners = max($maxSourceListeners, (int)($sourceProbe['listeners'] ?? 0));
    }
    $serverListeners = max(
        $currentListeners,
        (int)($autoSource['listeners'] ?? 0),
        $maxSourceListeners,
        (int)($global['listeners'] ?? 0)
    );
    if ($serverListeners <= 0 && $currentListeners > 0) {
        $serverListeners = $currentListeners;
    }
    $displayListeners = (int)($global['listener_connections'] ?? 0);
    if ($displayListeners <= 0) {
        $displayListeners = max(
            (int)($global['listeners'] ?? 0),
            $serverListeners,
            (int)($autoSource['listeners'] ?? 0),
            $currentListeners
        );
    }

    $data = [
        'ok' => true,
        'server_listeners' => $displayListeners,
        'current_listeners' => $currentListeners,
        'display_listeners' => $displayListeners,
        'total_listener_connections' => (int)($global['listener_connections'] ?? 0),
        'global_listeners' => (int)($global['listeners'] ?? 0),
        'public_status_ok' => ($publicXsl !== ''),
        'admin_status_ok' => ($xml !== '' || $xsl !== ''),
        'title' => $title,
        'artist' => $artist,
        'mount' => (string)($active['mount'] ?? ($cfg['mount_auto'] ?? '/autodj')),
        'display_show' => ($isLive ? 'Live DJ On Air' : 'FX Vibe Radio Flow'),
        'is_live' => $isLive,
        'status' => 'LIVE',
        'stream_url' => $playerUrls['preferred'],
        'public_stream_url' => $playerUrls['public'],
        'direct_stream_url' => $playerUrls['direct'],
        'proxy_stream_url' => $playerUrls['proxy'],
        'sources' => $sources,
        'source_count' => count($sources),
        'ts' => time(),
    ];

    $shouldWriteCache = (
        (int)($data['source_count'] ?? 0) > 0
        || !empty($data['public_status_ok'])
        || !empty($data['admin_status_ok'])
        || (int)($data['server_listeners'] ?? 0) > 0
        || trim((string)($data['title'] ?? '')) !== ''
    );
    if ($shouldWriteCache) {
        @file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), LOCK_EX);
    } elseif (is_file($cacheFile)) {
        @unlink($cacheFile);
    }
    return $cached = $data;
}



function ugfx_listener_count_fast(): int {
    static $count = null;
    if ($count !== null) return $count;

    $public = ugfx_parse_public_status_xsl(ugfx_fetch_public_status_xsl());
    $publicConnections = (int)($public['global']['listener_connections'] ?? 0);

    $adminXsl = ugfx_fetch_icecast_xsl();
    $admin = $adminXsl !== '' ? ugfx_parse_stats_xsl($adminXsl) : ugfx_parse_icecast_data(ugfx_fetch_icecast_xml());
    $adminConnections = (int)($admin['global']['listener_connections'] ?? 0);

    $status = ugfx_icecast_status();
    $statusConnections = (int)($status['total_listener_connections'] ?? 0);

    $bestConnections = max($publicConnections, $adminConnections, $statusConnections);
    if ($bestConnections > 0) {
        return $count = $bestConnections;
    }

    $publicListeners = (int)($public['global']['listeners'] ?? 0);
    $adminListeners = (int)($admin['global']['listeners'] ?? 0);
    $statusListeners = (int)($status['server_listeners'] ?? 0);

    return $count = max($publicListeners, $adminListeners, $statusListeners, 0);
}
function ugfx_now_playing(): array {
    $show = ugfx_current_show();
    $icecast = ugfx_icecast_status();
    return [
        'title' => $icecast['title'] ?: 'Underground FX Live',
        'artist' => $icecast['artist'] ?: $show['current'],
        'display_show' => $icecast['display_show'] ?? ($show['current'] ?? 'FX Vibe Radio Flow'),
        'album' => 'Underground FX Radio',
        'art' => ugfx_asset('img/brand/ugfx-hero.png'),
        'listeners' => ugfx_listener_count_fast(),
        'current_listeners' => (int)($icecast['current_listeners'] ?? 0),
        'total_listener_connections' => (int)($icecast['total_listener_connections'] ?? 0),
        'status' => $icecast['status'] ?? 'LIVE',
        'mount' => $icecast['mount'] ?? '',
        'is_live' => !empty($icecast['is_live']),
        'stream_url' => $icecast['stream_url'] ?? '',
        'public_stream_url' => $icecast['public_stream_url'] ?? '',
        'direct_stream_url' => $icecast['direct_stream_url'] ?? '',
        'proxy_stream_url' => $icecast['proxy_stream_url'] ?? '',
    ];
}


function ugfx_compute_listener_display_count(): int {
    return ugfx_listener_count_fast();
}

function ugfx_nav_items(): array {
    return [
        ['label' => 'Home', 'href' => 'index.php'],
        ['label' => 'Listen Live', 'href' => 'listen-live.php'],
        ['label' => 'Schedule', 'href' => 'schedule.php'],
        ['label' => 'Requests', 'href' => 'requests.php'],
        ['label' => 'News', 'href' => 'news.php'],
        ['label' => 'Team', 'href' => 'team.php'],
        ['label' => 'Join', 'href' => 'join.php'],
        ['label' => 'Events', 'href' => 'events.php'],
        ['label' => 'Friends', 'href' => 'friends.php'],
        ['label' => 'Chat', 'href' => 'chat.php'],
        ['label' => 'Contact', 'href' => 'contact.php'],
    ];
}

function ugfx_safe_links(string $text): string {
    $escaped = nl2br(htmlspecialchars($text, ENT_QUOTES, 'UTF-8'));
    return preg_replace_callback('/(https?:\/\/[^\s<]+)/i', function ($matches) {
        $url = htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8');
        return '<a href="' . $url . '" target="_blank" rel="noopener noreferrer nofollow">' . $url . '</a>';
    }, $escaped);
}


function ugfx_contains_banned_word(string $text): bool {
    global $ugfx;
    $words = ugfx_read_json($ugfx['storage']['banned_words'], []);
    if (!is_array($words) || !$words) return false;
    $lower = mb_strtolower($text, 'UTF-8');
    foreach ($words as $word) {
        $word = trim((string)$word);
        if ($word === '') continue;
        if (mb_stripos($lower, mb_strtolower($word, 'UTF-8'), 0, 'UTF-8') !== false) {
            return true;
        }
    }
    return false;
}

function ugfx_chat_messages(int $limit = 150): array {
    global $ugfx;
    $items = ugfx_read_json($ugfx['storage']['chat'], []);
    if (!is_array($items)) return [];
    $items = array_values(array_filter($items, function ($item) {
        return is_array($item) && !empty($item['id']);
    }));
    usort($items, function ($a, $b) {
        return strtotime((string)($a['created_at'] ?? '')) <=> strtotime((string)($b['created_at'] ?? ''));
    });
    if ($limit > 0 && count($items) > $limit) {
        $items = array_slice($items, -$limit);
    }
    return $items;
}

function ugfx_chat_add(array $message): array {
    global $ugfx;
    $items = ugfx_chat_messages(0);
    $id = (string)(time()) . bin2hex(random_bytes(4));
    $entry = [
        'id' => $message['id'] ?? $id,
        'name' => trim((string)($message['name'] ?? 'Guest')) ?: 'Guest',
        'message' => trim((string)($message['message'] ?? '')),
        'gif_url' => trim((string)($message['gif_url'] ?? '')),
        'sticker' => trim((string)($message['sticker'] ?? '')),
        'voice_url' => trim((string)($message['voice_url'] ?? '')),
        'voice_mime' => trim((string)($message['voice_mime'] ?? 'audio/webm')),
        'text_color' => preg_match('/^#[0-9A-Fa-f]{6}$/', (string)($message['text_color'] ?? '')) ? (string)$message['text_color'] : '#f3f7ff',
        'avatar' => trim((string)($message['avatar'] ?? '')),
        'type' => trim((string)($message['type'] ?? 'user')) ?: 'user',
        'created_at' => trim((string)($message['created_at'] ?? date('Y-m-d H:i:s'))),
    ];
    $items[] = $entry;
    ugfx_cleanup_chat_uploads(30);
    if (count($items) > 250) {
        $items = array_slice($items, -250);
    }
    ugfx_write_json($ugfx['storage']['chat'], array_values($items));
    return $entry;
}


function ugfx_base_url(): string {
    $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
    $scheme = $https ? 'https' : 'http';
    $host = trim((string)($_SERVER['HTTP_HOST'] ?? 'localhost')) ?: 'localhost';
    return $scheme . '://' . $host;
}

function ugfx_absolute_url(string $path = ''): string {
    $base = rtrim(ugfx_base_url(), '/');
    if ($path === '' || $path === '/') {
        return $base . '/';
    }
    if (preg_match('#^https?://#i', $path)) {
        return $path;
    }
    return $base . '/' . ltrim($path, '/');
}

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

function ugfx_page_seo(string $currentPage, string $pageTitle = ''): array {
    global $ugfx;
    $siteName = (string)($ugfx['site_name'] ?? 'Underground FX Radio');
    $tagline = trim((string)($ugfx['tagline'] ?? 'Underground FX Radio live.'));
    $path = strtolower(pathinfo($currentPage, PATHINFO_FILENAME));
    $descriptions = [
        'index' => 'Listen live to Underground FX Radio for nonstop music, live DJs, station news, requests, chat, events, and the full weekly schedule.',
        'listen-live' => 'Open the Underground FX live radio player and stream the station online with now playing information and direct access to the broadcast.',
        'schedule' => 'See the Underground FX Radio weekly schedule, live right now show, up next listing, and presenter timetable.',
        'requests' => 'Send a song request or shout-out to Underground FX Radio and connect directly with the station request line.',
        'news' => 'Read the latest Underground FX Radio news, station updates, announcements, featured shows, and community updates.',
        'team' => 'Meet the Underground FX presenters, DJs, and station team behind the shows and community programming.',
        'join' => 'Apply to join Underground FX Radio as a DJ, presenter, volunteer, or support team member.',
        'events' => 'Discover Underground FX and Broadcasturself events, featured promotions, music hunts, and station specials.',
        'friends' => 'Explore Underground FX friends, supporters, partner stations, and network links.',
        'chat' => 'Join the Underground FX website chat and connect with listeners, presenters, and the station community.',
        'contact' => 'Contact Underground FX Radio for station enquiries, promotions, partnerships, feedback, and support.',
        'charts' => 'Browse Underground FX charts, featured tracks, and station favourites.',
        'page' => $tagline,
        'status' => 'Underground FX Radio service status and station information.',
        'player-popup' => 'Underground FX Radio popout player.',
    ];
    $robots = [
        'chat' => 'noindex,follow,max-image-preview:large',
        'status' => 'noindex,nofollow',
        'player-popup' => 'noindex,nofollow',
    ];
    $description = $descriptions[$path] ?? $tagline;
    if ($path === 'page' && isset($_GET['slug'])) {
        $description = 'Read more from Underground FX Radio.';
    }
    $image = ugfx_absolute_url(ugfx_asset('img/brand/ugfx-hero.png'));
    $canonical = ugfx_current_url();
    $type = 'website';
    if ($path !== 'index') {
        $type = 'article';
    }
    return [
        'title' => trim($pageTitle) ?: $siteName,
        'description' => $description,
        'robots' => $robots[$path] ?? 'index,follow,max-image-preview:large',
        'canonical' => $canonical,
        'image' => $image,
        'image_alt' => $siteName . ' logo and hero artwork',
        'site_name' => $siteName,
        'type' => $type,
        'locale' => 'en_AU',
        'theme_color' => '#111418',
    ];
}

function ugfx_seo_json_ld(array $seo = []): string {
    global $ugfx;
    $siteName = (string)($ugfx['site_name'] ?? 'Underground FX Radio');
    $tagline = trim((string)($ugfx['tagline'] ?? 'Underground FX Radio live.'));
    $url = $seo['canonical'] ?? ugfx_current_url();
    $logo = $seo['image'] ?? ugfx_absolute_url(ugfx_asset('img/brand/ugfx-hero.png'));
    $sameAs = [];
    if (!empty($ugfx['socials']) && is_array($ugfx['socials'])) {
        foreach ($ugfx['socials'] as $social) {
            if (is_string($social) && preg_match('#^https?://#i', $social)) {
                $sameAs[] = $social;
            } elseif (is_array($social) && !empty($social['url']) && preg_match('#^https?://#i', (string)$social['url'])) {
                $sameAs[] = (string)$social['url'];
            }
        }
    }
    $graph = [
        [
            '@context' => 'https://schema.org',
            '@type' => 'Organization',
            '@id' => ugfx_base_url() . '#organization',
            'name' => $siteName,
            'url' => rtrim(ugfx_base_url(), '/') . '/',
            'logo' => [
                '@type' => 'ImageObject',
                'url' => $logo,
            ],
            'description' => $tagline,
            'sameAs' => array_values(array_unique($sameAs)),
        ],
        [
            '@context' => 'https://schema.org',
            '@type' => 'WebSite',
            '@id' => ugfx_base_url() . '#website',
            'url' => rtrim(ugfx_base_url(), '/') . '/',
            'name' => $siteName,
            'publisher' => ['@id' => ugfx_base_url() . '#organization'],
            'inLanguage' => 'en-AU',
        ],
        [
            '@context' => 'https://schema.org',
            '@type' => 'WebPage',
            '@id' => $url . '#webpage',
            'url' => $url,
            'name' => (string)($seo['title'] ?? $siteName),
            'description' => (string)($seo['description'] ?? $tagline),
            'isPartOf' => ['@id' => ugfx_base_url() . '#website'],
            'primaryImageOfPage' => [
                '@type' => 'ImageObject',
                'url' => $logo,
            ],
            'inLanguage' => 'en-AU',
        ],
    ];
    return json_encode($graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
