const TMDB_BASE = "https://api.themoviedb.org/3"; const LANGUAGE = "en-US"; export interface TmdbSearchResult { id: number; title: string; release_date: string; poster_path: string | null; overview: string; } export interface TmdbMovieDetails { id: number; title: string; release_date: string; runtime: number | null; overview: string; poster_path: string | null; backdrop_path: string | null; genres: Array<{ id: number; name: string }>; budget: number; revenue: number; production_countries: Array<{ iso_3166_1: string; name: string }>; production_companies: Array<{ name: string }>; spoken_languages: Array<{ english_name: string }>; release_dates?: { results: Array<{ iso_3166_1: string; release_dates: Array<{ certification: string; type: number; note: string; }>; }>; }; keywords?: { keywords: Array<{ id: number; name: string }>; }; credits?: { cast: Array<{ name: string; character: string }>; crew: Array<{ name: string; job: string; department: string }>; }; } function getApiKey(): string { const key = process.env.TMDB_API_KEY; if (!key) { throw new Error("TMDB_API_KEY is not set"); } return key; } async function tmdbFetch(path: string, params: Record = {}) { const url = new URL(`${TMDB_BASE}${path}`); url.searchParams.set("api_key", getApiKey()); url.searchParams.set("language", LANGUAGE); for (const [key, value] of Object.entries(params)) { url.searchParams.set(key, value); } const response = await fetch(url.toString(), { next: { revalidate: 3600 }, }); if (!response.ok) { throw new Error(`TMDB request failed: ${response.status}`); } return response.json() as Promise; } export async function searchMovies(query: string): Promise { if (!query.trim()) { return []; } const data = await tmdbFetch<{ results: TmdbSearchResult[] }>("/search/movie", { query: query.trim(), include_adult: "false", }); return data.results.slice(0, 8); } export async function getMovieDetails(movieId: number): Promise { return tmdbFetch(`/movie/${movieId}`, { append_to_response: "release_dates,credits,keywords", }); } export function getPosterUrl(path: string | null, size: "w185" | "w500" | "original" = "w500") { if (!path) { return null; } return `https://image.tmdb.org/t/p/${size}${path}`; } export function getUsCertification(movie: TmdbMovieDetails): string | null { const us = movie.release_dates?.results.find((r) => r.iso_3166_1 === "US"); if (!us) { return null; } const theatrical = us.release_dates.find((r) => r.type === 3 && r.certification); if (theatrical?.certification) { return theatrical.certification; } const any = us.release_dates.find((r) => r.certification); return any?.certification ?? null; } export function formatMovieContext(movie: TmdbMovieDetails): string { const genres = movie.genres.map((g) => g.name).join(", "); const keywords = movie.keywords?.keywords.map((k) => k.name).join(", ") ?? ""; const certification = getUsCertification(movie); const directors = movie.credits?.crew .filter((c) => c.job === "Director") .map((c) => c.name) .join(", "); const leadCast = movie.credits?.cast .slice(0, 5) .map((c) => `${c.name} as ${c.character}`) .join("; "); const releaseDates = movie.release_dates?.results .map((country) => { const certs = country.release_dates .filter((r) => r.certification) .map((r) => r.certification); const unique = [...new Set(certs)]; if (unique.length === 0) { return null; } return `${country.iso_3166_1}: ${unique.join(", ")}`; }) .filter(Boolean) .join("\n"); return [ `Title: ${movie.title}`, `Year: ${movie.release_date?.slice(0, 4) ?? "Unknown"}`, `Runtime: ${movie.runtime ?? "Unknown"} minutes`, `Genres: ${genres}`, `Overview: ${movie.overview}`, certification ? `US Certification: ${certification}` : null, keywords ? `Keywords: ${keywords}` : null, directors ? `Director(s): ${directors}` : null, leadCast ? `Lead cast: ${leadCast}` : null, movie.budget ? `Budget: $${movie.budget.toLocaleString("en-US")}` : null, movie.revenue ? `Revenue: $${movie.revenue.toLocaleString("en-US")}` : null, releaseDates ? `International certifications:\n${releaseDates}` : null, ] .filter(Boolean) .join("\n"); }