"use client" import { useCallback, useState, useEffect, useMemo, useRef } from "react" import Image from "next/image" import { Gamepad2Icon, MessageSquareIcon, SettingsIcon, TrendingUpIcon, ExternalLinkIcon, ClockIcon, SparklesIcon, DatabaseIcon, Plus, } from "lucide-react" import Link from "next/link" import { useSession } from "@/lib/auth-client" import { FaSteam } from "react-icons/fa" import { motion } from "motion/react" import { BookmarkButton } from "@/components/saved-games/bookmark-button" // Chart imports import { HistoricalAreaChart } from "@/components/charts/HistoricalAreaChart" import { UpscalerBarChart } from "@/components/charts/UpscalerBarChart" import { FpsBoxplot } from "@/components/charts/FpsBoxplot" import { FpsRangeChart } from "@/components/charts/FpsRangeChart" import { DeviceDonut } from "@/components/charts/DeviceDonut" import { TrustBar } from "@/components/charts/TrustBar" // Types interface Game { id: string steamAppId: number | null title: string description: string | null developer: string | null publisher: string | null genres: string[] | null headerImage: string | null capsuleImage: string | null storeUrl: string | null source: string lastSync: string | null syncStatus: string | null createdAt: string metascore?: number | null onlineMultiplayerStatus?: string | null } interface Counts { benchmarks: number presets: number comments: number } interface PlatformSupport { id: string gameId: string hardwareSlug: string isSupported: boolean protonStatus: string } interface Preset { id: string name: string description: string | null hardwareSlug: string hardwareName: string upvotes: number settingsCount: number fpsAvg: number | null fpsLow: number | null fpsHigh: number | null fsrVersion: string | null frameGenMethod: string | null protonVersion: string | null osVersion: string | null createdAt: string } interface StatsResponse { summary: { totalEntries: number avgFps: number bestDevice: string verifiedCount: number versionCount: number } isRawPerformer: boolean boxplot: Array<{ hardwareSlug: string hardwareName: string min: number q1: number median: number q3: number max: number }> historical: Array<{ period: string entries: Array<{ hardwareSlug: string avgFps: number count: number }> }> upscalerStats: Array<{ fsrVersion: string frameGenMethod: string hardwareSlug: string avgFps: number count: number }> fpsRange: Array<{ id: string hardwareSlug: string fpsLow: number fpsAvg: number fpsHigh: number isRawPerformer: boolean }> deviceBreakdown: Array<{ hardwareSlug: string hardwareName: string count: number }> trust: Array<{ id: string hardwareSlug: string upvotes: number downvotes: number verifiedAt: string | null userNotes: string | null createdAt: string }> filterOptions: { protonVersions: string[] osVersions: string[] } } interface Props { game: Game counts: Counts platformSupport: PlatformSupport[] presets: Preset[] gameId: string } const FSR_OPTIONS = ["Any", "None", "FSR1", "FSR2", "FSR3"] const FRAMEGEN_OPTIONS = ["Any", "None", "FSR FG", "DLSS FG"] function formatDate(value: string | null): string { if (!value) return "—" return new Date(value).toLocaleDateString() } function isRawPerformerPreset(preset: Preset): boolean { return ( preset.fpsAvg !== null && preset.fpsAvg >= 60 && preset.fsrVersion === "none" && preset.frameGenMethod === "none" ) } function getFpsColor(preset: Preset): string { if (preset.fpsAvg === null) return "text-text/60" if (isRawPerformerPreset(preset)) return "text-green-400" if (preset.frameGenMethod && preset.frameGenMethod !== "none") { if (preset.frameGenMethod === "dlss_fg") return "text-blue-400" return "text-orange-400" } if (preset.fsrVersion && preset.fsrVersion !== "none") return "text-orange-400" return "text-text/60" } export function GamePageClient({ game, counts, platformSupport, presets, gameId, }: Props) { const { data: session } = useSession() const [imgError, setImgError] = useState(false) const [stats, setStats] = useState(null) const [selectedDevices, setSelectedDevices] = useState([]) const [filters, setFilters] = useState({ proton: "all", os: "all", fsr: "all", frameGen: "all", }) const [loading, setLoading] = useState(true) const handleImgError = useCallback(() => setImgError(true), []) // Fetch stats on mount useEffect(() => { let cancelled = false async function fetchStats() { setLoading(true) try { const res = await fetch(`/api/games/${gameId}/stats`) if (!res.ok) throw new Error(`HTTP ${res.status}`) const data = await res.json() if (!cancelled) setStats(data) } catch (err) { console.error("Failed to fetch stats:", err) } finally { if (!cancelled) setLoading(false) } } fetchStats() return () => { cancelled = true } }, [gameId]) // Initialize selected devices when stats load const didInitDevices = useRef(false) useEffect(() => { if (stats && !didInitDevices.current) { didInitDevices.current = true const slugs = stats.deviceBreakdown.map((d) => d.hardwareSlug) queueMicrotask(() => setSelectedDevices(slugs)) } }, [stats]) // Filtered stats const filteredStats = useMemo(() => { if (!stats) return null const deviceSet = new Set(selectedDevices) const filterByDevice = (arr: T[]) => arr.filter((item) => deviceSet.has(item.hardwareSlug)) const filterUpscaler = (arr: StatsResponse["upscalerStats"]) => arr.filter((item) => { if (!deviceSet.has(item.hardwareSlug)) return false if (filters.fsr !== "all" && item.fsrVersion !== filters.fsr) return false if (filters.frameGen !== "all" && item.frameGenMethod !== filters.frameGen) return false return true }) return { ...stats, boxplot: filterByDevice(stats.boxplot), historical: stats.historical .map((h) => ({ ...h, entries: h.entries.filter((e) => deviceSet.has(e.hardwareSlug)), })) .filter((h) => h.entries.length > 0), upscalerStats: filterUpscaler(stats.upscalerStats), fpsRange: filterByDevice(stats.fpsRange), deviceBreakdown: filterByDevice(stats.deviceBreakdown), trust: filterByDevice(stats.trust), } }, [stats, selectedDevices, filters]) // Filtered presets const filteredPresets = useMemo(() => { return presets.filter((p) => { if (selectedDevices.length > 0 && !selectedDevices.includes(p.hardwareSlug)) return false if (filters.proton !== "all" && p.protonVersion !== filters.proton) return false if (filters.os !== "all" && p.osVersion !== filters.os) return false if (filters.fsr !== "all" && p.fsrVersion !== filters.fsr) return false if (filters.frameGen !== "all" && p.frameGenMethod !== filters.frameGen) return false return true }) }, [presets, selectedDevices, filters]) const coverImage = game.capsuleImage || game.headerImage const protonDbUrl = game.steamAppId ? `https://www.protondb.com/app/${game.steamAppId}` : null const steamDbUrl = game.steamAppId ? `https://steamdb.info/app/${game.steamAppId}` : null const toggleDevice = useCallback((slug: string) => { setSelectedDevices((prev) => prev.includes(slug) ? prev.filter((s) => s !== slug) : [...prev, slug] ) }, []) return (
{/* Section 1: Hero Header */}
{/* Cover image */}
{coverImage && !imgError ? ( {game.title} ) : (
)}
{/* Info */}
{/* Title row */}

{game.title}

{stats?.isRawPerformer && ( Raw Performer )}
{/* Subtitle */}
{game.developer && {game.developer}} {game.developer && game.publisher && ( · )} {game.publisher && {game.publisher}} {game.genres && game.genres.length > 0 && ( <> · {game.genres.slice(0, 3).join(", ")} )}
{/* Stats badges */}
{game.metascore !== undefined && game.metascore !== null && ( ★ {game.metascore} )} {session && ( Add Benchmark )}
{/* External links */}
{game.storeUrl && ( Steam )} {protonDbUrl && ( ProtonDB )} {steamDbUrl && ( SteamDB )}
{/* DB stats row */} {stats && (
{stats.summary.versionCount} versions · {stats.summary.verifiedCount} verified {game.lastSync && ( <> · Last sync {formatDate(game.lastSync)} )}
)}
{/* Section 2: Overview */}
{/* Left: Description */}

About

{game.description ? (

{game.description}

) : (

No description available.

)}
{/* Right: Metadata grid */}

Details

{game.steamAppId !== null && ( )} {game.onlineMultiplayerStatus && ( )}
{/* Platform Support */} {platformSupport.length > 0 && (

Platform Support

{platformSupport.map((ps) => (
{ps.hardwareSlug.replace(/-/g, " ")}
{ps.isSupported ? "Supported" : "Unsupported"} {ps.protonStatus}
))}
)}
{/* Section 3: Device Selector + Filters */}
{/* Device ribbon */} {stats && stats.deviceBreakdown.length > 0 && (
{stats.deviceBreakdown.map((device) => { const active = selectedDevices.includes(device.hardwareSlug) return ( ) })}
)} {/* Fine filters */}
setFilters((f) => ({ ...f, proton: v }))} /> setFilters((f) => ({ ...f, os: v }))} /> o.toLowerCase())} onChange={(v) => setFilters((f) => ({ ...f, fsr: v }))} /> o.toLowerCase().replace(" ", "_") )} onChange={(v) => setFilters((f) => ({ ...f, frameGen: v }))} />
{/* Section 4: Statistics Dashboard */}
{/* Row 1 — Featured */} {filteredStats && (

Historical Performance

{filteredStats.historical.length > 0 ? ( ) : (
No historical data
)}
)} {/* Loading state */} {loading && (
Loading statistics...
)} {/* Row 2 — Upscaler Bar */} {filteredStats && filteredStats.upscalerStats.length > 0 && (

Upscaler Performance

)} {/* Row 3 — 3-column grid */} {filteredStats && (
{filteredStats.boxplot.length > 0 && (

FPS Distribution

)} {filteredStats.fpsRange.length > 0 && (

FPS Range

)} {filteredStats.deviceBreakdown.length > 0 && (

Device Breakdown

)}
)} {/* Row 4 — Trust Bar */} {filteredStats && filteredStats.trust.length > 0 && (

Community Trust

)}
{/* Section 5: Community Presets */}

Community Presets

{filteredPresets.length} preset{filteredPresets.length !== 1 ? "s" : ""}
{filteredPresets.length === 0 ? (

No presets match the selected filters

) : (
{filteredPresets.map((preset) => { const raw = isRawPerformerPreset(preset) const fpsColor = getFpsColor(preset) return (
{/* Header */}

{preset.name}

{raw && ( Raw )}

{preset.hardwareName}

{preset.upvotes}
{/* Settings count */}
{preset.settingsCount} settings
{/* FPS */} {preset.fpsAvg !== null && (
{preset.fpsAvg} avg {preset.fpsLow !== null && ( {" "}({preset.fpsLow}–{preset.fpsHigh}) )}
)} {/* Technology tags */}
{preset.fsrVersion && preset.fsrVersion !== "none" && ( {preset.fsrVersion.toUpperCase()} )} {preset.frameGenMethod && preset.frameGenMethod !== "none" && ( {preset.frameGenMethod === "fsr_fg" ? "FSR FG" : preset.frameGenMethod === "dlss_fg" ? "DLSS FG" : preset.frameGenMethod} )}
{/* Proton + OS */}
{preset.protonVersion && ( Proton {preset.protonVersion} )} {preset.osVersion && ( SteamOS {preset.osVersion} )}
) })}
)}
) } function Badge({ icon: Icon, value, label, }: { icon: React.ElementType value: number label: string }) { return ( {value} {label} ) } function MetaItem({ label, value }: { label: string; value: string }) { return (
{label} {value}
) } function StatCard({ label, value, icon: Icon, }: { label: string value: string icon: React.ElementType }) { return (
{label} {value}
) } function FilterSelect({ label, value, options, onChange, }: { label: string value: string options: string[] onChange: (value: string) => void }) { return (
) }