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; } }