import { createFileRoute, Link } from "@tanstack/react-router"
import { useTranslation } from "react-i18next"
import { Bookmark, BookmarkCheck, Globe, Star, TrendingUp, User, Users } from "lucide-react"
import { BackButton } from "@/components/back-button"
import { StarDisplay } from "@/components/star-display"
import { RatingHistogram } from "@/components/rating-histogram"
import { EmptyState } from "@/components/empty-state"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
import { posterUrl, tmdbProfileUrl } from "@/lib/api/client"
import { timeAgo, shortDate } from "@/lib/date"
import { useMovie, useMovieHistory, useMovieProfile } from "@/hooks/use-movies"
import { useDocumentTitle } from "@/hooks/use-document-title"
import {
useWatchlistStatus,
useAddToWatchlist,
useRemoveFromWatchlist,
} from "@/hooks/use-watchlist"
import type { CastMemberDto, CrewMemberDto } from "@/lib/api/movies"
export const Route = createFileRoute("/_app/movies/$id")({
component: MovieDetailPage,
})
function MovieDetailPage() {
const { t } = useTranslation()
const { id } = Route.useParams()
const { data, isPending } = useMovie(id)
const { data: profile } = useMovieProfile(id)
const { data: history } = useMovieHistory(id)
if (isPending) return
if (!data) return null
const { movie, stats, reviews } = data
useDocumentTitle(movie.title)
const hasStats = profile && (profile.budget_usd != null || profile.revenue_usd != null || profile.vote_average != null)
return (
{(profile?.overview ?? movie.overview) && (
{profile?.overview ?? movie.overview}
)}
{hasStats && (
{profile.budget_usd != null && (
${(profile.budget_usd / 1e6).toFixed(0)}M
{t("movie.budget")}
)}
{profile.revenue_usd != null && (
${(profile.revenue_usd / 1e6).toFixed(0)}M
{t("movie.revenue")}
)}
{profile.vote_average != null && (
{profile.vote_average.toFixed(1)}
{t("movie.tmdb")}
)}
)}
{stats.rating_histogram.length > 0 && (
{t("movie.ratingDistribution")}
)}
{profile && profile.cast.length > 0 && (
)}
{profile && profile.crew.length > 0 && (
)}
{profile && profile.keywords.length > 0 && (
{t("movie.keywords")}
{profile.keywords.map((k) => (
{k.name}
))}
)}
{t("movie.community")}
{!reviews.items.length ? (
) : (
{reviews.items.map((r, i) => (
{r.user_display}
{r.is_federated && }
{timeAgo(r.watched_at)}
{r.comment && (
{r.comment}
)}
))}
)}
{history && history.viewings.length > 0 && (
{t("movie.yourHistory")}
{history.trend && (
{t("movie.trend", { trend: history.trend })}
)}
{history.viewings.map((v) => (
{shortDate(v.watched_at)}
{v.comment && (
{v.comment}
)}
))}
)}
)
}
function HeroSection({
movie,
stats,
movieId,
tagline,
}: {
movie: { title: string; release_year: number; director?: string; poster_path?: string; genres: string[]; runtime_minutes?: number }
stats: { total_count: number; avg_rating?: number; federated_count: number }
movieId: string
tagline?: string
}) {
const { t } = useTranslation()
const { data: watchlistData } = useWatchlistStatus(movieId)
const addWatchlist = useAddToWatchlist()
const removeWatchlist = useRemoveFromWatchlist()
const onWatchlist = watchlistData?.on_watchlist ?? false
return (
{movie.poster_path && (
})
)}
{movie.title}
{movie.release_year}
{movie.director && ` · ${movie.director}`}
{movie.runtime_minutes && ` · ${movie.runtime_minutes}m`}
{tagline &&
{tagline}
}
{movie.genres.length > 0 && (
{movie.genres.map((g) => (
{g}
))}
)}
{stats.avg_rating != null && (
{stats.avg_rating.toFixed(1)}
)}
{t("common.reviews", { count: stats.total_count })}
)
}
function PersonStrip({ items, type }: { items: (CastMemberDto | CrewMemberDto)[]; type: "cast" | "crew" }) {
return (
{items.map((person, i) => {
const subtitle = type === "cast"
? (person as CastMemberDto).character
: (person as CrewMemberDto).job
return (
{person.profile_path ? (
})
) : (
)}
{person.name}
{subtitle}
)
})}
)
}
function DetailSkeleton() {
return (
{[1, 2, 3].map((i) => (
))}
)
}