using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Services;
///
/// Service for managing topic/category data and filtering.
///
public class CategoryService
{
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly TimeSpan _topicsCacheDuration = TimeSpan.FromHours(24);
private Dictionary? _topicsCache;
private DateTime _topicsCacheExpiry = DateTime.MinValue;
///
/// Initializes a new instance of the class.
///
/// The logger factory.
public CategoryService(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger();
}
///
/// Gets all topics for a business unit.
///
/// The business unit.
/// The cancellation token.
/// List of topics.
public async Task> GetTopicsAsync(string businessUnit, CancellationToken cancellationToken = default)
{
// Return cached topics if still valid
if (_topicsCache != null && DateTime.UtcNow < _topicsCacheExpiry)
{
_logger.LogDebug("Returning cached topics for business unit: {BusinessUnit}", businessUnit);
return _topicsCache.Values.ToList();
}
_logger.LogInformation("Fetching topics for business unit: {BusinessUnit}", businessUnit);
using var apiClient = new SRFApiClient(_loggerFactory);
var topics = await apiClient.GetAllTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
if (topics != null && topics.Count > 0)
{
// Cache topics by ID for quick lookups
_topicsCache = topics
.Where(t => !string.IsNullOrEmpty(t.Id))
.ToDictionary(t => t.Id!, t => t);
_topicsCacheExpiry = DateTime.UtcNow.Add(_topicsCacheDuration);
_logger.LogInformation("Cached {Count} topics for business unit: {BusinessUnit}", _topicsCache.Count, businessUnit);
}
return topics ?? new List();
}
///
/// Gets a topic by ID.
///
/// The topic ID.
/// The business unit.
/// The cancellation token.
/// The topic, or null if not found.
public async Task GetTopicByIdAsync(string topicId, string businessUnit, CancellationToken cancellationToken = default)
{
// Ensure topics are loaded
if (_topicsCache == null || DateTime.UtcNow >= _topicsCacheExpiry)
{
await GetTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
}
return _topicsCache?.GetValueOrDefault(topicId);
}
///
/// Filters shows by topic ID.
///
/// The shows to filter.
/// The topic ID to filter by.
/// Filtered list of shows.
public IReadOnlyList FilterShowsByTopic(IReadOnlyList shows, string topicId)
{
if (string.IsNullOrEmpty(topicId))
{
return shows;
}
return shows
.Where(s => s.TopicList != null && s.TopicList.Contains(topicId))
.ToList();
}
///
/// Groups shows by their topics.
///
/// The shows to group.
/// Dictionary mapping topic IDs to shows.
public IReadOnlyDictionary> GroupShowsByTopics(IReadOnlyList shows)
{
var groupedShows = new Dictionary>();
foreach (var show in shows)
{
if (show.TopicList == null || show.TopicList.Count == 0)
{
continue;
}
foreach (var topicId in show.TopicList)
{
if (!groupedShows.TryGetValue(topicId, out var showList))
{
showList = new List();
groupedShows[topicId] = showList;
}
showList.Add(show);
}
}
return groupedShows;
}
///
/// Gets shows for a specific topic, sorted by number of episodes.
///
/// The topic ID.
/// The business unit.
/// Maximum number of results to return.
/// The cancellation token.
/// List of shows for the topic.
public async Task> GetShowsByTopicAsync(
string topicId,
string businessUnit,
int maxResults = 50,
CancellationToken cancellationToken = default)
{
using var apiClient = new SRFApiClient(_loggerFactory);
var allShows = await apiClient.GetAllShowsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
if (allShows == null || allShows.Count == 0)
{
_logger.LogWarning("No shows available for business unit: {BusinessUnit}", businessUnit);
return new List();
}
var filteredShows = FilterShowsByTopic(allShows, topicId)
.Where(s => s.NumberOfEpisodes > 0)
.OrderByDescending(s => s.NumberOfEpisodes)
.Take(maxResults)
.ToList();
_logger.LogDebug("Found {Count} shows for topic {TopicId}", filteredShows.Count, topicId);
return filteredShows;
}
///
/// Gets video count for each topic.
///
/// The shows to analyze.
/// Dictionary mapping topic IDs to video counts.
public IReadOnlyDictionary GetVideoCountByTopic(IReadOnlyList shows)
{
var topicCounts = new Dictionary();
foreach (var show in shows)
{
if (show.TopicList == null || show.TopicList.Count == 0)
{
continue;
}
foreach (var topicId in show.TopicList)
{
if (!topicCounts.TryGetValue(topicId, out var count))
{
count = 0;
}
topicCounts[topicId] = count + show.NumberOfEpisodes;
}
}
return topicCounts;
}
///
/// Clears the topics cache.
///
public void ClearCache()
{
_topicsCache = null;
_topicsCacheExpiry = DateTime.MinValue;
_logger.LogInformation("Topics cache cleared");
}
}