namespace TeleWave.Domain.Library;
///
/// Переиспользуемое шоу в общей библиотеке: сериал (упорядоченные серии) либо полнометражка.
/// Серии идут строго в порядке ; курсор показа на канале хранится
/// отдельно на связке канал↔шоу (см. Broadcast/ChannelShow).
///
public class Show
{
private readonly List _episodes = new();
public Guid Id { get; private set; }
public string Name { get; private set; } = string.Empty;
/// Оригинальное название (обычно на английском) — по нему ищутся метаданные; на экранах
/// продолжаем показывать . Null/пусто — ищем по .
public string? OriginalName { get; private set; }
public string? Description { get; private set; }
public ShowKind Kind { get; private set; }
public DateTimeOffset CreatedAt { get; private set; }
// ── Метаданные (TMDb/OMDb/вручную) ──
/// Источник метаданных: «tmdb»/«omdb»/«manual» или null, если не заданы.
public string? MetadataProvider { get; private set; }
/// Идентификатор шоу во внешнем источнике (для довыгрузки серий).
public string? MetadataExternalId { get; private set; }
public int? Year { get; private set; }
/// Постер шоу — ссылка на запись общего реестра изображений (Domain/Images) или null.
public Guid? PosterImageId { get; private set; }
/// Серии шоу (backing-field для EF). Порядок показа — по ;
/// потребители сортируют явно (см. загрузчик планировщика).
public IReadOnlyList Episodes => _episodes;
private Show() { }
public static Show Create(
string name,
ShowKind kind,
string? description = null,
string? originalName = null
) =>
new()
{
Id = Guid.NewGuid(),
Name = name,
OriginalName = Normalize(originalName),
Kind = kind,
Description = description,
CreatedAt = DateTimeOffset.UtcNow,
};
public void Rename(string name, string? description)
{
Name = name;
Description = description;
}
/// Задать/снять оригинальное название (пустая строка трактуется как отсутствие).
public void SetOriginalName(string? originalName) => OriginalName = Normalize(originalName);
private static string? Normalize(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
/// Добавляет серию в конец. Для допустима ровно одна серия.
public ShowEpisode AddEpisode(Guid mediaAssetId)
{
var nextPosition = _episodes.Count == 0 ? 0 : _episodes.Max(e => e.Position) + 1;
var episode = ShowEpisode.Create(Id, mediaAssetId, nextPosition);
_episodes.Add(episode);
return episode;
}
public bool RemoveEpisode(Guid episodeId)
{
var episode = _episodes.FirstOrDefault(e => e.Id == episodeId);
if (episode is null)
return false;
_episodes.Remove(episode);
return true;
}
public bool CanAddEpisode => Kind != ShowKind.Single || _episodes.Count == 0;
/// Применить метаданные из внешнего источника. Постер (уже зарегистрирован в реестре) может быть null.
public void ApplyMetadata(
string provider,
string externalId,
string? description,
int? year,
Guid? posterImageId
)
{
MetadataProvider = provider;
MetadataExternalId = externalId;
if (!string.IsNullOrWhiteSpace(description))
Description = description;
Year = year;
if (posterImageId is not null)
PosterImageId = posterImageId;
}
/// Ручная правка метаданных (без внешнего источника).
public void UpdateMetadataManual(string? description, int? year)
{
MetadataProvider = "manual";
MetadataExternalId = null;
Description = description;
Year = year;
}
/// Привязать/снять постер шоу (ссылка на запись реестра изображений).
public void SetPosterImage(Guid? imageId) => PosterImageId = imageId;
/// Сбросить все метаданные и отвязать постер (сама картинка остаётся в галерее).
public void ClearMetadata()
{
MetadataProvider = null;
MetadataExternalId = null;
Year = null;
PosterImageId = null;
Description = null;
}
}