'int', 'number_of_episodes' => 'int', 'rating' => 'float', 'is_favorite' => 'bool', 'first_air_date' => 'date', 'last_air_date' => 'date', 'metadata' => 'array' ]; /** * Get all actors associated with this TV show */ public function actors() { $stmt = $this->pdo->prepare(" SELECT a.* FROM actors a JOIN actor_tv_show ats ON a.id = ats.actor_id WHERE ats.tv_show_id = :tv_show_id ORDER BY a.name ASC "); $stmt->execute(['tv_show_id' => $this->id]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } /** * Get TV show statistics */ public static function getStats(\PDO $pdo): array { $stmt = $pdo->query(" SELECT COUNT(*) as total_tv_shows, COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_tv_shows, AVG(rating) as avg_rating FROM tv_shows "); return $stmt->fetch(\PDO::FETCH_ASSOC); } /** * Get total count with optional search */ public static function getTotalCount(\PDO $pdo, string $search = ''): int { $sql = "SELECT COUNT(*) as count FROM tv_shows t JOIN sources s ON t.source_id = s.id"; $params = []; if (!empty($search)) { $sql .= " WHERE t.title LIKE :search"; $params['search'] = "%{$search}%"; } $stmt = $pdo->prepare($sql); $stmt->execute($params); return (int) $stmt->fetch()['count']; } /** * Get all TV shows with pagination and optional search */ public static function getAllWithPagination(\PDO $pdo, int $page, int $perPage, string $search = ''): array { $offset = ($page - 1) * $perPage; $sql = " SELECT t.*, s.display_name as source_name FROM tv_shows t JOIN sources s ON t.source_id = s.id "; $params = []; if (!empty($search)) { $sql .= " WHERE t.title LIKE :search"; $params['search'] = "%{$search}%"; } $sql .= " ORDER BY t.title ASC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT); foreach ($params as $key => $value) { $stmt->bindValue(":{$key}", $value); } $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } /** * Toggle favorite status */ public function toggleFavorite(): bool { return $this->update($this->id, [ 'is_favorite' => !$this->is_favorite ]); } /** * Update rating */ public function updateRating(float $rating): bool { return $this->update($this->id, [ 'rating' => min(10.0, max(0.0, $rating)) ]); } /** * Get the source relationship */ public function source(): ?Source { $stmt = $this->pdo->prepare("SELECT * FROM sources WHERE id = :source_id"); $stmt->execute(['source_id' => $this->source_id]); $sourceData = $stmt->fetch(\PDO::FETCH_ASSOC); return $sourceData ? new Source($this->pdo, $sourceData) : null; } public function getSeasonsWithEpisodes(): array { $stmt = $this->pdo->prepare(" SELECT s.*, COUNT(e.id) as episode_count, SUM(CASE WHEN e.watched = 1 THEN 1 ELSE 0 END) as watched_episodes FROM seasons s LEFT JOIN episodes e ON s.id = e.season_id WHERE s.tv_show_id = :tv_show_id GROUP BY s.id ORDER BY s.season_number ASC "); $stmt->execute(['tv_show_id' => $this->id]); $seasons = $stmt->fetchAll(\PDO::FETCH_ASSOC); // Get episodes for each season foreach ($seasons as &$season) { $stmt = $this->pdo->prepare(" SELECT e.*, (SELECT COUNT(*) FROM user_episodes WHERE episode_id = e.id AND watched = 1) as watch_count FROM episodes e WHERE e.season_id = :season_id ORDER BY e.episode_number ASC "); $stmt->execute(['season_id' => $season['id']]); $season['episodes'] = $stmt->fetchAll(\PDO::FETCH_ASSOC); } return $seasons; } /** * Get similar TV shows based on genres */ public function getSimilarShows(int $limit = 6): array { $genres = $this->genre ? explode(',', $this->genre) : []; $placeholders = str_repeat('?,', count($genres) - 1) . '?'; $sql = " SELECT t.*, COUNT(DISTINCT g.genre) as matching_genres FROM tv_shows t CROSS JOIN (SELECT TRIM(value) as genre FROM json_each('[\"" . str_replace(',', '","', str_replace('"', '\\"', $this->genre)) . "\"]') WHERE value != '') g WHERE t.id != ? AND t.genre LIKE '%' || g.genre || '%' GROUP BY t.id HAVING matching_genres > 0 ORDER BY matching_genres DESC, t.rating DESC LIMIT ? "; $params = array_merge([$this->id, $limit]); $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } /** * Record that this TV show was viewed */ public function recordView(): bool { $stmt = $this->pdo->prepare(" INSERT OR REPLACE INTO tv_show_views (tv_show_id, view_count, last_viewed_at) VALUES (?, COALESCE((SELECT view_count FROM tv_show_views WHERE tv_show_id = ?), 0) + 1, CURRENT_TIMESTAMP) "); return $stmt->execute([$this->id, $this->id]); } /** * Get all available genres from TV shows */ public static function getAvailableGenres(\PDO $pdo): array { $stmt = $pdo->query(" SELECT DISTINCT TRIM(value) as genre FROM tv_shows, json_each('[\"' || REPLACE(genre, ',', '\",\"') || '\"]') WHERE genre IS NOT NULL AND genre != '' ORDER BY genre "); return $stmt->fetchAll(\PDO::FETCH_COLUMN); } /** * Get all available years from TV shows' first_air_date */ public static function getAvailableYears(\PDO $pdo): array { $stmt = $pdo->query(" SELECT DISTINCT strftime('%Y', first_air_date) as year FROM tv_shows WHERE first_air_date IS NOT NULL ORDER BY year DESC "); return $stmt->fetchAll(\PDO::FETCH_COLUMN); } }