"use client"; import { createContext, useCallback, useContext, useMemo, useState, } from "react"; import { toast } from "sonner"; import type { Season } from "@/lib/types/title"; import { unwatchEpisodeAction, unwatchSeasonAction, updateTitleRating, updateTitleStatus, watchEpisode, watchMovie, watchSeason, } from "./actions"; interface TitleInteractionState { titleId: string; titleType: "movie" | "tv"; titleName: string; userStatus: string | null; userRating: number; episodeWatches: string[]; seasons: Season[]; handleStatusChange: (status: string | null) => void; handleRating: (ratingStars: number) => void; handleWatchMovie: () => void; handleWatchEpisode: ( episodeId: string, seasonNum: number, epNum: number, isWatched: boolean, ) => void; handleMarkSeason: (season: Season) => void; handleUnmarkSeason: (season: Season) => void; watchingEp: string | null; } const TitleInteractionContext = createContext( null, ); export function useTitleInteraction() { const ctx = useContext(TitleInteractionContext); if (!ctx) throw new Error( "useTitleInteraction must be used within TitleInteractionProvider", ); return ctx; } export function TitleInteractionProvider({ titleId, titleType, titleName, initialStatus, initialRating, initialEpisodeWatches, seasons, children, }: { titleId: string; titleType: "movie" | "tv"; titleName: string; initialStatus: string | null; initialRating: number; initialEpisodeWatches: string[]; seasons: Season[]; children: React.ReactNode; }) { const [userStatus, setUserStatus] = useState(initialStatus); const [userRating, setUserRating] = useState(initialRating); const [episodeWatches, setEpisodeWatches] = useState(initialEpisodeWatches); const [watchingEp, setWatchingEp] = useState(null); const handleStatusChange = useCallback( async (status: string | null) => { const prev = userStatus; const prevWatches = episodeWatches; setUserStatus(status); if (status === "completed" && titleType === "tv") { const allEpIds = seasons.flatMap((s) => s.episodes.map((ep) => ep.id)); setEpisodeWatches(allEpIds); } try { await updateTitleStatus(titleId, status); const label = status === "watchlist" ? "Added to watchlist" : status === "in_progress" ? "Marked as watching" : status === "completed" ? "Marked as completed" : "Removed from list"; toast.success(label); } catch { setUserStatus(prev); setEpisodeWatches(prevWatches); toast.error("Failed to update status"); } }, [titleId, titleType, userStatus, episodeWatches, seasons], ); const handleRating = useCallback( async (ratingStars: number) => { const prev = userRating; setUserRating(ratingStars); try { await updateTitleRating(titleId, ratingStars); toast.success( ratingStars > 0 ? `Rated ${ratingStars} star${ratingStars > 1 ? "s" : ""}` : "Rating removed", ); } catch { setUserRating(prev); toast.error("Failed to update rating"); } }, [titleId, userRating], ); const handleWatchMovie = useCallback(async () => { const prev = userStatus; setUserStatus("completed"); try { await watchMovie(titleId); toast.success(`Marked "${titleName}" as watched`); } catch { setUserStatus(prev); toast.error("Failed to mark as watched"); } }, [titleId, titleName, userStatus]); const handleWatchEpisode = useCallback( async ( episodeId: string, seasonNum: number, epNum: number, isWatched: boolean, ) => { setWatchingEp(episodeId); if (isWatched) { setEpisodeWatches((w) => w.filter((id) => id !== episodeId)); setUserStatus((s) => (s === "completed" ? "in_progress" : s)); try { await unwatchEpisodeAction(episodeId); toast.success(`Unwatched S${seasonNum} E${epNum}`); } catch { setEpisodeWatches((w) => w.includes(episodeId) ? w : [...w, episodeId], ); toast.error("Failed to unmark episode"); } } else { setEpisodeWatches((w) => w.includes(episodeId) ? w : [...w, episodeId], ); setUserStatus((s) => s ?? "in_progress"); try { await watchEpisode(episodeId); toast.success(`Watched S${seasonNum} E${epNum}`); } catch { setEpisodeWatches((w) => w.filter((id) => id !== episodeId)); toast.error("Failed to mark episode"); } } setWatchingEp(null); }, [], ); const handleMarkSeason = useCallback( async (season: Season) => { const unwatched = season.episodes.filter( (ep) => !episodeWatches.includes(ep.id), ); if (unwatched.length === 0) return; setEpisodeWatches((w) => { const set = new Set(w); for (const ep of unwatched) set.add(ep.id); return [...set]; }); try { await watchSeason(season.id); toast.success( `Watched all of ${season.name ?? `Season ${season.seasonNumber}`}`, ); } catch { toast.error("Failed to mark some episodes"); } }, [episodeWatches], ); const handleUnmarkSeason = useCallback(async (season: Season) => { const seasonEpIds = new Set(season.episodes.map((ep) => ep.id)); setEpisodeWatches((w) => w.filter((id) => !seasonEpIds.has(id))); setUserStatus((s) => (s === "completed" ? "in_progress" : s)); try { await unwatchSeasonAction(season.id); toast.success( `Unwatched all of ${season.name ?? `Season ${season.seasonNumber}`}`, ); } catch { toast.error("Failed to unmark some episodes"); } }, []); const value = useMemo( () => ({ titleId, titleType, titleName, userStatus, userRating, episodeWatches, seasons, handleStatusChange, handleRating, handleWatchMovie, handleWatchEpisode, handleMarkSeason, handleUnmarkSeason, watchingEp, }), [ titleId, titleType, titleName, userStatus, userRating, episodeWatches, seasons, handleStatusChange, handleRating, handleWatchMovie, handleWatchEpisode, handleMarkSeason, handleUnmarkSeason, watchingEp, ], ); return ( {children} ); }