'int', 'number_of_episodes' => 'int', 'rating' => 'float', 'is_favorite' => 'bool', 'first_air_date' => 'date', 'last_air_date' => 'date', 'metadata' => 'array' ]; /** * Remove an actor from this TV show */ public function removeActor(int $actorId): bool { $stmt = $this->pdo->prepare(" DELETE FROM actor_tv_show WHERE tv_show_id = :tv_show_id AND actor_id = :actor_id "); return $stmt->execute([ 'tv_show_id' => $this->id, 'actor_id' => $actorId ]); } /** * Update the cast field with actor names */ public function updateCastField(): bool { $actors = $this->actors(); $actorNames = array_column($actors, 'name'); $castString = implode(', ', $actorNames); return $this->update($this->id, [ 'cast' => $castString ]); } /** * 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 { // Get all episodes for this TV show, grouped by season $stmt = $this->pdo->prepare(" SELECT season_number, COUNT(*) as episode_count, SUM(CASE WHEN watched = 1 THEN 1 ELSE 0 END) as watched_episodes FROM tv_episodes WHERE tv_show_id = :tv_show_id GROUP BY season_number ORDER BY season_number ASC "); $stmt->execute(['tv_show_id' => $this->id]); $seasonStats = $stmt->fetchAll(\PDO::FETCH_ASSOC); $seasons = []; // For each season, get the episodes and create a season object foreach ($seasonStats as $stat) { $seasonNumber = $stat['season_number']; // Get episodes for this season $stmt = $this->pdo->prepare(" SELECT e.* FROM tv_episodes e WHERE e.tv_show_id = :tv_show_id AND e.season_number = :season_number ORDER BY e.episode_number ASC "); $stmt->execute([ 'tv_show_id' => $this->id, 'season_number' => $seasonNumber ]); $episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC); // Create a season object (simulating the old seasons table structure) $seasons[] = [ 'id' => null, // No seasons table, so no ID 'season_number' => $seasonNumber, 'episode_count' => (int)$stat['episode_count'], 'watched_episodes' => (int)$stat['watched_episodes'], 'episodes' => $episodes ]; } 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); } }