from __future__ import annotations from datetime import datetime from typing import List, Optional from pydantic import BaseModel, Field, HttpUrl class EpisodeInfo(BaseModel): """Information about a single episode.""" episode_number: int = Field(..., ge=1, description="Episode index (1-based)") title: Optional[str] = Field(None, description="Optional episode title") aired_at: Optional[datetime] = Field(None, description="Air date/time if known") duration_seconds: Optional[int] = Field(None, ge=0, description="Duration in seconds") available: bool = Field(True, description="Whether the episode is available for download") sources: List[HttpUrl] = Field(default_factory=list, description="List of known streaming/download source URLs") class MissingEpisodeInfo(BaseModel): """Represents a gap in the episode list for a series.""" from_episode: int = Field(..., ge=1, description="Starting missing episode number") to_episode: int = Field(..., ge=1, description="Ending missing episode number (inclusive)") reason: Optional[str] = Field(None, description="Optional explanation why episodes are missing") @property def count(self) -> int: """Number of missing episodes in the range.""" return max(0, self.to_episode - self.from_episode + 1) class AnimeSeriesResponse(BaseModel): """Response model for a series with metadata and episodes.""" id: str = Field(..., description="Unique series identifier") title: str = Field(..., description="Series title") alt_titles: List[str] = Field(default_factory=list, description="Alternative titles") description: Optional[str] = Field(None, description="Short series description") total_episodes: Optional[int] = Field(None, ge=0, description="Declared total episode count if known") episodes: List[EpisodeInfo] = Field(default_factory=list, description="Known episodes information") missing_episodes: List[MissingEpisodeInfo] = Field(default_factory=list, description="Detected missing episode ranges") thumbnail: Optional[HttpUrl] = Field(None, description="Optional thumbnail image URL") class SearchRequest(BaseModel): """Request payload for searching series.""" query: str = Field(..., min_length=1) limit: int = Field(10, ge=1, le=100) include_adult: bool = Field(False) class SearchResult(BaseModel): """Search result item for a series discovery endpoint.""" id: str title: str snippet: Optional[str] = None thumbnail: Optional[HttpUrl] = None score: Optional[float] = None