<?php

// Charger les fonctions de sécurité
require_once __DIR__ . '/security.php';

// Charger le système de cache simple
require_once __DIR__ . '/simple-cache.php';

// Charger les fonctions WordPress
require_once __DIR__ . '/wordpress.php';

// Charger d'abord la configuration locale si elle existe
$config_local_file = __DIR__ . '/config.local.php';
if (file_exists($config_local_file)) {
    require_once $config_local_file;
}

// Charger la configuration par défaut si elle existe
$config_default_file = __DIR__ . '/config.default.php';
if (file_exists($config_default_file)) {
    require_once $config_default_file;
}

// Locale et fuseau horaire
setlocale(LC_TIME, 'fr_FR.UTF-8');
date_default_timezone_set(DEFAULT_TIMEZONE);

// Initialisation des catégories de vidéo depuis l'API
$peertube_categories = initCategories();
define('PEERTUBE_CATEGORIES', $peertube_categories);

/**
 * Initialise et récupère les catégories depuis l'API PeerTube
 *
 * @return array Liste des catégories
 */
function initCategories() {
    // Récupérer la liste des catégories depuis l'API
    $categories = callPeerTubeApi('videos/categories');

    // Tableau de correspondance pour traduire les catégories en français
    $translations = [
        'Music' => 'Musique',
        'Films' => 'Films',
        'Vehicles' => 'Véhicules',
        'Art' => 'Art',
        'Sports' => 'Sports',
        'Travels' => 'Voyages',
        'Gaming' => 'Jeux vidéo',
        'People' => 'Personnes',
        'Comedy' => 'Humour',
        'Entertainment' => 'Divertissement',
        'News & Politics' => 'Actualités & Politique',
        'How To' => 'Tutoriels',
        'Education' => 'Éducation',
        'Activism' => 'Activisme',
        'Science & Technology' => 'Science & Technologie',
        'Animals' => 'Animaux',
        'Kids' => 'Enfants',
        'Food' => 'Cuisine',
    ];

    // Si une constante PRIORITY_CATEGORIES est définie, utiliser ces traductions
    if (defined('PRIORITY_CATEGORIES')) {
        $priorityCategories = PRIORITY_CATEGORIES;
        foreach ($priorityCategories as $id => $name) {
            // Trouver la clé anglaise correspondant à l'ID
            $englishName = array_search($id, array_keys($categories));
            if ($englishName !== false) {
                $translations[$englishName] = $name;
            }
        }
    }

    $result = [];
    foreach ($categories as $key => $name) {
        // Utiliser la traduction si disponible, sinon garder le nom original
        $translatedName = isset($translations[$name]) ? $translations[$name] : $name;
        $result[$key] = $translatedName;
    }

    return $result;
}

/**
 * Version originale pour appeler l'API PeerTube (sans cache)
 * Cette fonction est maintenant utilisée en interne par callPeerTubeApi
 *
 * @param string $endpoint Point de terminaison de l'API
 * @param array $params Paramètres optionnels pour la requête
 * @return array Données retournées par l'API
 */
function callPeerTubeApiOriginal($endpoint, $params = []) {
    // Validation de l'URL de base PeerTube pour prévenir SSRF
    if (!isValidPeerTubeUrl(PEERTUBE_URL)) {
        error_log('SECURITY: Invalid PeerTube URL detected: ' . PEERTUBE_URL);
        return [];
    }

    // Nettoyer et valider l'endpoint
    $endpoint = ltrim($endpoint, '/');
    if (!isValidApiEndpoint($endpoint)) {
        error_log('SECURITY: Invalid API endpoint detected: ' . $endpoint);
        return [];
    }

    $url = PEERTUBE_URL . '/api/v1/' . $endpoint;

    // Ajouter les paramètres à l'URL
    if (!empty($params)) {
        $url .= '?' . http_build_query($params);
    }

    // Initialiser cURL avec options de sécurité
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($ch, CURLOPT_USERAGENT, 'fediverse-oki/1.0');

    // Ajouter la clé API si définie
    if (defined('API_KEY') && !empty(API_KEY)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: ApiKey ' . API_KEY
        ]);
    }

    // Exécuter la requête
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    curl_close($ch);

    // Traiter la réponse
    if ($response === false || !empty($error)) {
        error_log('PeerTube API error: ' . $error);
        return [];
    }

    if ($httpCode < 200 || $httpCode >= 300) {
        error_log('PeerTube API HTTP error: ' . $httpCode);
        return [];
    }

    // Décoder la réponse JSON
    $data = json_decode($response, true);

    return $data ?: [];
}

/**
 * Fonction utilitaire pour appeler l'API PeerTube avec cache
 *
 * @param string $endpoint Point de terminaison de l'API
 * @param array $params Paramètres optionnels pour la requête
 * @return array Données retournées par l'API
 */
function callPeerTubeApi($endpoint, $params = []) {
    // Utiliser la fonction cachée si disponible
    if (function_exists('callPeerTubeApiCached')) {
        return callPeerTubeApiCached($endpoint, $params);
    }

    // Fallback vers la version originale
    return callPeerTubeApiOriginal($endpoint, $params);
}

/**
 * Valide l'URL PeerTube pour prévenir les attaques SSRF
 *
 * @param string $url URL à valider
 * @return bool True si l'URL est valide et sûre
 */
function isValidPeerTubeUrl($url) {
    // Vérifier que l'URL est bien formée
    $parsed = parse_url($url);
    if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) {
        return false;
    }

    // Autoriser uniquement HTTPS (ou HTTP en développement)
    if (!in_array($parsed['scheme'], ['https', 'http'])) {
        return false;
    }

    // Bloquer les adresses IP privées et locales
    $host = $parsed['host'];
    if (filter_var($host, FILTER_VALIDATE_IP)) {
        if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return false;
        }
    }

    // Bloquer localhost et autres domaines dangereux
    $blockedHosts = ['localhost', '127.0.0.1', '::1', '0.0.0.0', 'metadata.google.internal'];
    if (in_array(strtolower($host), $blockedHosts)) {
        return false;
    }

    return true;
}

/**
 * Valide l'endpoint API pour prévenir l'injection de chemins
 *
 * @param string $endpoint Endpoint à valider
 * @return bool True si l'endpoint est valide
 */
function isValidApiEndpoint($endpoint) {
    // Bloquer les tentatives de path traversal
    if (strpos($endpoint, '..') !== false || strpos($endpoint, '//') !== false) {
        return false;
    }

    // Autoriser uniquement les caractères alphanumériques, tirets, underscores et slashes
    if (!preg_match('/^[a-zA-Z0-9\/_-]+$/', $endpoint)) {
        return false;
    }

    // Liste blanche des endpoints autorisés
    $allowedEndpoints = [
        'videos',
        'videos/categories',
        'search/videos',
        'videos/.*', // Pour les endpoints dynamiques comme videos/{id}
        'videos/.*/comment-threads', // Pour les commentaires
        'accounts',
        'accounts/.*/videos' // Pour les vidéos d'un compte spécifique
    ];

    foreach ($allowedEndpoints as $pattern) {
        // Remplacer les .* par des marqueurs temporaires
        $tempPattern = str_replace('.*', '__WILDCARD__', $pattern);
        // Échapper les caractères spéciaux regex
        $escapedPattern = preg_quote($tempPattern, '/');
        // Remettre les wildcards en place
        $regexPattern = str_replace('__WILDCARD__', '.*', $escapedPattern);

        if (preg_match('/^' . $regexPattern . '$/', $endpoint)) {
            return true;
        }
    }

    return false;
}

/**
 * Récupère les catégories depuis l'API PeerTube
 *
 * @return array Liste des catégories
 */
function getCategories() {
    // Utiliser les catégories déjà récupérées
    $categories = PEERTUBE_CATEGORIES;

    $result = [];
    foreach ($categories as $key => $name) {
        $result[] = [
            'id' => $key,
            'name' => $name
        ];
    }

    return $result;
}

/**
 * Récupère les vidéos récentes depuis l'API PeerTube
 *
 * @param int $count Nombre de vidéos à récupérer
 * @return array Liste des vidéos récentes
 */
function getRecentVideos($count = RECENT_VIDEOS_COUNT) {
    // Récupérer les vidéos récentes
    $data = callPeerTubeApi('videos', [
        'sort' => '-publishedAt',
        'count' => $count,
        'isLocal' => true
    ]);

    return formatVideosData($data['data'] ?? []);
}

/**
 * Récupère les vidéos tendances depuis l'API PeerTube
 *
 * @param int $count Nombre de vidéos à récupérer
 * @return array Liste des vidéos tendances
 */
function getTrendingVideos($count = TRENDING_VIDEOS_COUNT) {
    // Récupérer les vidéos tendances
    $data = callPeerTubeApi('videos', [
        'sort' => '-trending',
        'count' => $count,
        'isLocal' => true
    ]);

    return formatVideosData($data['data'] ?? []);
}

/**
 * Récupère les vidéos avec un tag spécifique depuis l'API PeerTube
 *
 * @param string $tag Tag à filtrer
 * @param int $count Nombre de vidéos à récupérer
 * @return array Liste des vidéos
 */
function getVideosByTag($tag, $count) {
    // Récupérer les vidéos par tag
    $data = callPeerTubeApi('videos', [
        'tagsOneOf' => $tag,
        'count' => $count,
        'isLocal' => true
    ]);

    return formatVideosData($data['data'] ?? []);
}

/**
 * Récupère les shorts (vidéos courtes) depuis l'API PeerTube
 * Les shorts sont des vidéos locales de moins de 2 minutes
 *
 * @param int $count Nombre de shorts à récupérer
 * @return array Liste des shorts
 */
function getShorts($count = SHORTS_COUNT) {
    // Récupérer plus de vidéos que nécessaire pour pouvoir filtrer
    $data = callPeerTubeApi('videos', [
        'sort' => '-publishedAt', // Les plus récentes d'abord
        'count' => SHORTS_COUNT_SEARCH,
        'isLocal' => true
    ]);

    // Formater les données
    $allVideos = formatVideosData($data['data'] ?? []);

    // Filtrer pour ne garder que les vidéos de moins de 2 minutes (120 secondes) et en mode portrait
    $shortVideos = array_filter($allVideos, function($video) {
        // Vérifier la durée (moins de 2 minutes)
        $durationOk = $video['duration'] < SHORTS_MAX_DURATION;

        // Vérifier le ratio (mode portrait)
        $ratioOk = isset($video['aspectRatio']) && $video['aspectRatio'] <= 1;

        return $durationOk && $ratioOk;
    });

    // Limiter au nombre demandé
    return array_slice($shortVideos, 0, $count);
}

/**
 * Récupère les vidéos sur l'indépendance depuis l'API PeerTube
 *
 * @param int $count Nombre de vidéos à récupérer
 * @return array Liste des vidéos sur l'indépendance
 */
function getIndependenceVideos($count = INDEPENDENCE_VIDEOS_COUNT) {
    // Récupérer les vidéos sur l'indépendance
    return getVideosByTag(TAG_INDEPENDENCE, $count);
}

/**
 * Vérifie s'il y a un direct en cours du compte LIVE_ACCOUNT_NAME sur l'instance PeerTube
 *
 * @return array|null Informations sur le direct en cours ou null si aucun direct
 */
function getLiveStream() {
    // Récupérer les lives du compte spécifié
    $accountName = LIVE_ACCOUNT_NAME;
    $data = callPeerTubeApi('accounts/' . $accountName . '/videos', [
        'count' => 1,
        'isLocal' => true,
        'isLive' => true,  // Filtrer uniquement les lives
        'sort' => '-publishedAt'  // Les plus récents en premier
    ]);

    // Vérifier si on a des résultats
    if (empty($data['data']) || count($data['data']) === 0) {
        return null;
    }

    // Formater les données du live
    $liveData = formatVideosData($data['data']);

    // Filtrer pour ne garder que les lives en cours
    $activeLives = array_filter($liveData, function($video) {
        return isset($video['isLive']) && $video['isLive'] === true;
    });

    // Retourner le premier live trouvé
    return !empty($activeLives) ? reset($activeLives) : null;
}

/**
 * Formate les données brutes des vidéos venant de l'API
 *
 * @param array $videosData Données brutes des vidéos
 * @return array Données formatées
 */
function formatVideosData($videosData) {
    $videos = [];

    foreach ($videosData as $video) {
        // Récupérer la vignette (thumbnail)
        $thumbnail = isset($video['previewPath'])
            ? PEERTUBE_URL . $video['previewPath']
            : 'img/default-thumbnail.jpg';

        // Récupérer l'avatar de la chaîne
        $channelAvatar = isset($video['channel']['avatars'][0]['path']) && isset($video['channel']['avatars'][0]['path'])
            ? PEERTUBE_URL . $video['channel']['avatars'][0]['path']
            : 'img/default-avatar.png';

        // Formater les données
        $videos[] = [
            'id' => $video['uuid'],
            'title' => $video['name'],
            'thumbnail' => $thumbnail,
            'duration' => $video['duration'],
            'channel' => $video['channel']['displayName'],
            'channelAvatar' => $channelAvatar,
            'views' => $video['views'],
            'date' => $video['publishedAt'],
            'aspectRatio' => $video['aspectRatio'],
            'description' => $video['description'] ?? '',
            'tags' => $video['tags'] ?? [],
            'isLive' => isset($video['isLive']) ? $video['isLive'] : false
        ];
    }

    return $videos;
}

// Fonctions utilitaires pour formater les données d'affichage
function formatDuration($seconds) {
    $hours = floor($seconds / 3600);
    $minutes = floor(($seconds % 3600) / 60);
    $remainingSeconds = $seconds % 60;

    if ($hours > 0) {
        return sprintf('%d:%02d:%02d', $hours, $minutes, $remainingSeconds);
    } else {
        return sprintf('%d:%02d', $minutes, $remainingSeconds);
    }
}

function formatViewCount($views) {
    if ($views >= 1000000) {
        return round($views / 1000000, 1) . 'M';
    } elseif ($views >= 1000) {
        return round($views / 1000, 1) . 'K';
    } else {
        return $views;
    }
}

function formatDate($dateString) {
    $date = new DateTime($dateString);
    $now = new DateTime();
    $interval = $now->diff($date);

    if ($interval->days == 0) {
        return 'Aujourd\'hui';
    } elseif ($interval->days == 1) {
        return 'Hier';
    } elseif ($interval->days < 7) {
        return 'Il y a ' . $interval->days . ' jours';
    } elseif ($interval->days < 30) {
        $weeks = floor($interval->days / 7);
        return 'Il y a ' . $weeks . ' semaine' . ($weeks > 1 ? 's' : '');
    } elseif ($interval->days < 365) {
        $months = floor($interval->days / 30);
        return 'Il y a ' . $months . ' mois';
    } else {
        $years = floor($interval->days / 365);
        return 'Il y a ' . $years . ' an' . ($years > 1 ? 's' : '');
    }
}

/**
 * Récupère les vidéos d'une catégorie spécifique depuis l'API PeerTube
 *
 * @param int $categoryId Identifiant de la catégorie
 * @param int $count Nombre de vidéos à récupérer
 * @return array Liste des vidéos de la catégorie
 */
function getVideosByCategory($categoryId, $count = CATEGORY_VIDEOS_COUNT) {
    // Récupérer les vidéos par catégorie
    $data = callPeerTubeApi('videos', [
        'categoryOneOf' => $categoryId,
        'count' => $count,
        'sort' => '-publishedAt', // Les plus récentes d'abord
        'isLocal' => true
    ]);

    return formatVideosData($data['data'] ?? []);
}

/**
 * Récupère la liste des catégories à afficher (triées selon les priorités)
 *
 * @return array Liste des catégories avec id, name et videos
 */
function getDisplayCategories() {
    $categories = [];
    $priorityCategories = PRIORITY_CATEGORIES;
    $allCategories = PEERTUBE_CATEGORIES;

    // Ajouter uniquement les catégories prioritaires dans l'ordre défini
    foreach ($priorityCategories as $catId => $categoryName) {
        $videos = getVideosByCategory($catId);

        // N'ajouter que les catégories qui ont des vidéos
        if (!empty($videos)) {
            $categories[] = [
                'id' => $catId,
                'name' => $categoryName,
                'videos' => $videos
            ];
        }
    }

    return $categories;
}

/**
 * Récupère les commentaires d'une vidéo
 * @param string $videoId ID de la vidéo
 * @return array Tableau des commentaires
 */
function getVideoComments($videoId) {
    $endpoint = "videos/{$videoId}/comment-threads";
    $response = callPeerTubeApi($endpoint);

    if (!$response || !isset($response['data'])) {
        return [];
    }

    return $response['data'];
}

/**
 * Récupère les options de téléchargement pour une vidéo
 * @param string $videoId ID de la vidéo
 * @return array Options de téléchargement
 */
function getVideoDownloadOptions($videoId) {
    // Récupérer les informations complètes de la vidéo
    $videoData = callPeerTubeApi('videos/' . $videoId);

    $downloadOptions = [];

    // Ajouter les fichiers directs s'ils existent
    if (isset($videoData['files']) && !empty($videoData['files'])) {
        foreach ($videoData['files'] as $file) {
            if (isset($file['fileDownloadUrl']) && !empty($file['fileDownloadUrl'])) {
                $downloadOptions[] = [
                    'type' => 'direct',
                    'url' => PEERTUBE_URL . $file['fileDownloadUrl'],
                    'resolution' => isset($file['resolution']['label']) ? $file['resolution']['label'] : 'Original',
                    'size' => isset($file['size']) ? formatFileSize($file['size']) : 'Inconnu'
                ];
            }
        }
    }

    // Ajouter les playlists de streaming s'ils existent
    if (isset($videoData['streamingPlaylists']) && !empty($videoData['streamingPlaylists'])) {
        foreach ($videoData['streamingPlaylists'] as $playlist) {
            if (isset($playlist['files']) && !empty($playlist['files'])) {
                foreach ($playlist['files'] as $file) {
                    if (isset($file['fileDownloadUrl']) && !empty($file['fileDownloadUrl'])) {
                        $downloadOptions[] = [
                            'type' => 'hls',
                            'url' => $file['fileDownloadUrl'],
                            'resolution' => isset($file['resolution']['label']) ? $file['resolution']['label'] : 'Original',
                            'size' => isset($file['size']) ? formatFileSize($file['size']) : 'Inconnu'
                        ];
                    }
                }
            }
        }
    }

    return $downloadOptions;
}

/**
 * Formate la taille d'un fichier en format lisible
 * @param int $bytes Taille en octets
 * @return string Taille formatée
 */
function formatFileSize($bytes) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];

    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);

    $bytes /= (1 << (10 * $pow));

    return round($bytes, 2) . ' ' . $units[$pow];
}

/**
 * Recherche des vidéos selon un critère
 *
 * @param string $query Terme de recherche
 * @param int $count Nombre de vidéos à récupérer
 * @param int $start Index de départ pour la pagination
 * @return array Liste des vidéos correspondant à la recherche
 */
function searchVideos($query, $count = COUNT_VIDEO_SEARCH, $start = 0) {
    if (empty($query)) {
        return [];
    }

    // Vérifier si la recherche concerne un tag (commence par #)
    $isTagSearch = false;
    if (substr($query, 0, 1) === '#') {
        $isTagSearch = true;
        $tag = substr($query, 1); // Enlever le # du début

        // Récupérer les vidéos avec ce tag via l'API
        $data = callPeerTubeApi('videos', [
            'tagsOneOf' => $tag,
            'count' => $count,
            'start' => $start,
            'isLocal' => true, // Uniquement les vidéos locales
            'sort' => '-publishedAt' // Les plus récentes d'abord
        ]);

        return formatVideosData($data['data'] ?? []);
    }

    // Recherche normale (pas un tag)
    $data = callPeerTubeApi('search/videos', [
        'search' => $query,
        'count' => $count,
        'start' => $start,
        'isLocal' => true, // Uniquement les vidéos locales
        'sort' => '-publishedAt' // Les plus récentes d'abord
    ]);
    return formatVideosData($data['data'] ?? []);
}

/**
 * Récupère les derniers épisodes d'un ou plusieurs podcasts Castopod via leurs feeds RSS
 *
 * @param string $castopodUrl URL de l'instance Castopod
 * @param array|string $podcastSlugs Slug(s) du/des podcast(s) - tableau ou chaîne unique
 * @param int $count Nombre d'épisodes à récupérer (total)
 * @return array Liste des épisodes formatés, triés par date
 */
function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count = 5) {
    if (!defined('CASTOPOD_ENABLED') || !CASTOPOD_ENABLED) {
        return [];
    }

    $castopodUrl = $castopodUrl ?? CASTOPOD_URL;
    $podcastSlugs = $podcastSlugs ?? (defined('CASTOPOD_PODCAST_SLUGS') ? CASTOPOD_PODCAST_SLUGS : ['annu_kute_cedric']);
    $count = $count ?? CASTOPOD_EPISODES_COUNT;

    // Convertir en tableau si c'est une chaîne unique (rétrocompatibilité)
    if (is_string($podcastSlugs)) {
        $podcastSlugs = [$podcastSlugs];
    }

    // Clé de cache unique pour la combinaison de podcasts
    $cacheKey = 'castopod_' . md5($castopodUrl . implode('_', $podcastSlugs) . '_' . $count);

    // Vérifier le cache
    if (defined('CACHE_ENABLED') && CACHE_ENABLED) {
        $cachedData = getFromCache($cacheKey);
        if ($cachedData !== null) {
            return $cachedData;
        }
    }

    $allEpisodes = [];

    // Itérer sur chaque podcast
    foreach ($podcastSlugs as $index => $podcastSlug) {
        try {
            // Ajouter un délai entre les requêtes pour éviter le rate limiting (sauf pour la première)
            // Note: Castopod a un rate limit strict, le cache est fortement recommandé
            if ($index > 0) {
                sleep(5); // 5 secondes de délai minimum pour Castopod
            }

            // Construire l'URL du feed RSS pour ce podcast
            $feedUrl = rtrim($castopodUrl, '/') . '/@' . $podcastSlug . '/feed';

            // Récupérer le feed RSS
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $feedUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            $xmlContent = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            if ($httpCode !== 200 || !$xmlContent) {
                continue; // Passer au podcast suivant en cas d'erreur
            }

            $episodes = [];

            // Vérifier si SimpleXML est disponible
            if (function_exists('simplexml_load_string')) {
                // Parser avec SimpleXML (méthode recommandée)
                $xml = simplexml_load_string($xmlContent);
                if (!$xml) {
                    continue; // Passer au podcast suivant
                }

                // Enregistrer les namespaces
                $namespaces = $xml->getNamespaces(true);

                // Extraire les informations du podcast
                $podcastTitle = (string) $xml->channel->title;
                $podcastImage = (string) $xml->channel->image->url;
                $podcastLink = (string) $xml->channel->link;

                // Parcourir les items (épisodes) - on récupère tous pour trier après
                foreach ($xml->channel->item as $item) {

                // Extraire les infos de l'épisode
                $itunesNs = $item->children($namespaces['itunes'] ?? 'http://www.itunes.com/dtds/podcast-1.0.dtd');

                // Récupérer l'image de l'épisode
                $episodeImage = $podcastImage;
                if (isset($itunesNs->image)) {
                    $imageAttrs = $itunesNs->image->attributes();
                    if (isset($imageAttrs['href'])) {
                        $episodeImage = (string) $imageAttrs['href'];
                    }
                }

                    $episode = [
                        'title' => (string) $item->title,
                        'link' => (string) $item->link,
                        'pubDate' => (string) $item->pubDate,
                        'description' => strip_tags((string) $item->description),
                        'duration' => (string) $itunesNs->duration ?? '',
                        'image' => $episodeImage,
                        'audioUrl' => (string) $item->enclosure['url'] ?? '',
                        'podcastTitle' => $podcastTitle,
                        'podcastLink' => $podcastLink,
                        'podcastSlug' => $podcastSlug,
                    ];

                    // Formater la date
                    if ($episode['pubDate']) {
                        try {
                            $date = new DateTime($episode['pubDate']);
                            $episode['formattedDate'] = $date->format('d/m/Y');
                            $episode['timestamp'] = $date->getTimestamp();
                        } catch (Exception $e) {
                            $episode['formattedDate'] = '';
                            $episode['timestamp'] = 0;
                        }
                    }

                    // Formater la durée
                    if ($episode['duration']) {
                        $episode['formattedDuration'] = formatDuration($episode['duration']);
                    } else {
                        $episode['formattedDuration'] = '';
                    }

                    // Limiter la description
                    if (strlen($episode['description']) > 150) {
                        $episode['description'] = substr($episode['description'], 0, 150) . '...';
                    }

                    $episodes[] = $episode;
                }
            } else {
                // Fallback: Parser avec regex (moins fiable mais fonctionne sans SimpleXML)
                // Extraire les infos du podcast
                preg_match('/<channel>.*?<title>(.*?)<\/title>/s', $xmlContent, $podcastTitleMatch);
                preg_match('/<channel>.*?<link>(.*?)<\/link>/s', $xmlContent, $podcastLinkMatch);
                preg_match('/<image>.*?<url>(.*?)<\/url>/s', $xmlContent, $podcastImageMatch);

                $podcastTitle = $podcastTitleMatch[1] ?? '';
                $podcastLink = $podcastLinkMatch[1] ?? '';
                $podcastImage = $podcastImageMatch[1] ?? '';

                // Extraire tous les items
                preg_match_all('/<item>(.*?)<\/item>/s', $xmlContent, $items);

                foreach ($items[1] as $itemContent) {
                    // Extraire les données de chaque item
                    preg_match('/<title>(.*?)<\/title>/', $itemContent, $titleMatch);
                    preg_match('/<link>(.*?)<\/link>/', $itemContent, $linkMatch);
                    preg_match('/<pubDate>(.*?)<\/pubDate>/', $itemContent, $dateMatch);
                    preg_match('/<description>(.*?)<\/description>/s', $itemContent, $descMatch);
                    preg_match('/<itunes:duration>(.*?)<\/itunes:duration>/', $itemContent, $durationMatch);
                    preg_match('/<itunes:image[^>]*href=["\']([^"\']*)["\']/', $itemContent, $imageMatch);
                    preg_match('/<enclosure[^>]*url=["\']([^"\']*)["\']/', $itemContent, $audioMatch);

                    $episode = [
                        'title' => $titleMatch[1] ?? '',
                        'link' => $linkMatch[1] ?? '',
                        'pubDate' => $dateMatch[1] ?? '',
                        'description' => strip_tags($descMatch[1] ?? ''),
                        'duration' => $durationMatch[1] ?? '',
                        'image' => $imageMatch[1] ?? $podcastImage,
                        'audioUrl' => $audioMatch[1] ?? '',
                        'podcastTitle' => $podcastTitle,
                        'podcastLink' => $podcastLink,
                        'podcastSlug' => $podcastSlug,
                    ];

                    // Formater la date
                    if ($episode['pubDate']) {
                        try {
                            $date = new DateTime($episode['pubDate']);
                            $episode['formattedDate'] = $date->format('d/m/Y');
                            $episode['timestamp'] = $date->getTimestamp();
                        } catch (Exception $e) {
                            $episode['formattedDate'] = '';
                            $episode['timestamp'] = 0;
                        }
                    }

                    // Formater la durée
                    if ($episode['duration']) {
                        $episode['formattedDuration'] = formatDuration($episode['duration']);
                    } else {
                        $episode['formattedDuration'] = '';
                    }

                    // Limiter la description
                    if (strlen($episode['description']) > 150) {
                        $episode['description'] = substr($episode['description'], 0, 150) . '...';
                    }

                    $episodes[] = $episode;
                }
            }

            // Ajouter les épisodes de ce podcast à la liste globale
            $allEpisodes = array_merge($allEpisodes, $episodes);

        } catch (Exception $e) {
            error_log('Erreur lors de la récupération des épisodes Castopod pour ' . $podcastSlug . ': ' . $e->getMessage());
            // Continuer avec le podcast suivant
        }
    }

    // Trier tous les épisodes par date (du plus récent au plus ancien)
    usort($allEpisodes, function($a, $b) {
        return ($b['timestamp'] ?? 0) - ($a['timestamp'] ?? 0);
    });

    // Limiter au nombre demandé
    $allEpisodes = array_slice($allEpisodes, 0, $count);

    // Mettre en cache (uniquement si non vide : une erreur temporaire
    // — rate limit HTTP 429, timeout… — ne doit pas être figée en cache)
    if (!empty($allEpisodes) && defined('CACHE_ENABLED') && CACHE_ENABLED) {
        saveToCache($cacheKey, $allEpisodes);
    }

    return $allEpisodes;
}

/**
 * Récupère des morceaux aléatoires depuis Funkwhale via l'API
 *
 * @param string $funkwhaleUrl URL de l'instance Funkwhale
 * @param int $count Nombre de morceaux à récupérer
 * @return array Liste des morceaux formatés
 */
function getFunkwhaleTracks($funkwhaleUrl = null, $count = null) {
    if (!defined('FUNKWHALE_ENABLED') || !FUNKWHALE_ENABLED) {
        return [];
    }

    $funkwhaleUrl = $funkwhaleUrl ?? FUNKWHALE_URL;
    $count = $count ?? FUNKWHALE_TRACKS_COUNT;

    // Clé de cache - on récupère un grand nombre de morceaux pour le cache
    $cacheKey = 'funkwhale_' . md5($funkwhaleUrl);
    $cacheFetchSize = 50; // Nombre de morceaux à mettre en cache

    // Vérifier le cache
    $allTracks = null;
    if (defined('CACHE_ENABLED') && CACHE_ENABLED) {
        $allTracks = getFromCache($cacheKey);
    }

    // Si pas de cache, récupérer depuis l'API
    if ($allTracks === null) {
        try {
            // Construire l'URL de l'API avec filtre pour morceaux locaux uniquement
            // Le paramètre scope=me récupère uniquement les morceaux de l'instance locale
            $apiUrl = rtrim($funkwhaleUrl, '/') . '/api/v1/tracks?ordering=random&page_size=' . $cacheFetchSize . '&scope=all';

            // Récupérer les morceaux via cURL
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $apiUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            $jsonContent = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            if ($httpCode !== 200 || !$jsonContent) {
                return [];
            }

            // Parser le JSON
            $data = json_decode($jsonContent, true);
            if (!$data || !isset($data['results'])) {
                return [];
            }

            $allTracks = [];

            // Extraire le domaine de l'instance locale pour filtrer
            $localDomain = parse_url($funkwhaleUrl, PHP_URL_HOST);

            // Parcourir les morceaux et filtrer pour garder uniquement les morceaux locaux
            foreach ($data['results'] as $item) {
                // Vérifier si le morceau est local en comparant le domaine
                // Les morceaux fédérés ont généralement un domaine différent dans leur URL
                $isLocal = true;

                // Vérifier via l'URL de l'artiste ou de l'album
                if (isset($item['artist']['channel'])) {
                    $artistDomain = parse_url($item['artist']['channel'], PHP_URL_HOST);
                    if ($artistDomain && $artistDomain !== $localDomain) {
                        $isLocal = false;
                    }
                }

                // Ou vérifier via le champ is_local si disponible
                if (isset($item['is_local']) && !$item['is_local']) {
                    $isLocal = false;
                }

                // Ne garder que les morceaux locaux
                if ($isLocal) {
                    // Récupérer l'URL d'écoute depuis l'API (peut être relative)
                    $listenUrl = $item['listen_url'] ?? '';

                    // Si l'URL est relative, la transformer en URL absolue
                    if (!empty($listenUrl) && strpos($listenUrl, 'http') !== 0) {
                        $listenUrl = rtrim($funkwhaleUrl, '/') . $listenUrl;
                    }

                    $track = [
                        'title' => $item['title'] ?? '',
                        'artist' => $item['artist']['name'] ?? 'Artiste inconnu',
                        'album' => $item['album']['title'] ?? '',
                        'cover' => $item['album']['cover']['urls']['medium_square_crop'] ?? '',
                        'duration' => $item['uploads'][0]['duration'] ?? 0,
                        'link' => rtrim($funkwhaleUrl, '/') . '/library/tracks/' . ($item['id'] ?? ''),
                        'trackId' => $item['id'] ?? 0,
                        'audioUrl' => $listenUrl,
                    ];

                    // Formater la durée
                    if ($track['duration']) {
                        $track['formattedDuration'] = formatDuration($track['duration']);
                    } else {
                        $track['formattedDuration'] = '';
                    }

                    $allTracks[] = $track;
                }
            }

            // Mettre en cache tous les morceaux locaux
            if (defined('CACHE_ENABLED') && CACHE_ENABLED) {
                saveToCache($cacheKey, $allTracks);
            }

        } catch (Exception $e) {
            error_log('Erreur lors de la récupération des morceaux Funkwhale: ' . $e->getMessage());
            return [];
        }
    }

    // Si pas assez de morceaux, retourner ce qu'on a
    if (empty($allTracks)) {
        return [];
    }

    // Sélectionner aléatoirement $count morceaux parmi tous les morceaux en cache
    $trackCount = count($allTracks);
    if ($trackCount <= $count) {
        return $allTracks;
    }

    // Mélanger et prendre les N premiers
    shuffle($allTracks);
    return array_slice($allTracks, 0, $count);
}
?>
