import { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Alert, Animated, Easing, KeyboardAvoidingView, Modal, Platform, ScrollView, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import { useTranslation } from 'react-i18next'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Haptics from 'expo-haptics'; import { Ionicons } from '@expo/vector-icons'; import { RiveAvatar } from '../RiveAvatar'; import { StarRating } from './StarRating'; import { Button } from '../Button'; import { useColors } from '../../lib/theme'; import { apiFetch } from '../../lib/api'; export type GameOverScreenProps = { score: number; bestScore: number; gameName: string; scoreLabel?: string; goodScore?: number; onRetry: () => void; onExit: () => void; isNewBest?: boolean; }; function lyraMsg( gameName: string, score: number, goodScore: number, isNewBest: boolean, t: (k: string) => string ): { title: string; body: string } { if (isNewBest) return { title: t('gameOver.lyra_title_record'), body: t('gameOver.lyra_body_record') }; if (score >= goodScore) return { title: t('gameOver.lyra_title_good'), body: t('gameOver.lyra_body_good') }; if (score > 0) return { title: t('gameOver.lyra_title_ok'), body: t('gameOver.lyra_body_ok') }; return { title: t('gameOver.lyra_title_low'), body: t('gameOver.lyra_body_low') }; } export function GameOverScreen({ score, bestScore, gameName, scoreLabel, goodScore = 5, onRetry, onExit, isNewBest = false, }: GameOverScreenProps) { const { t } = useTranslation(); const colors = useColors(); const insets = useSafeAreaInsets(); const slideAnim = useRef(new Animated.Value(500)).current; const [rating, setRating] = useState(0); const [feedback, setFeedback] = useState(''); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [shareSectionOpen, setShareSectionOpen] = useState(false); const [shareText, setShareText] = useState(''); const lyraShareTextRef = useRef(''); const [shareTextLoading, setShareTextLoading] = useState(false); const [regenLoading, setRegenLoading] = useState(false); const [sharing, setSharing] = useState(false); const [posted, setPosted] = useState(false); const [postError, setPostError] = useState(false); const emotion = isNewBest || score >= goodScore ? 'happy' : 'empathy'; const msg = lyraMsg(gameName, score, goodScore, isNewBest, t); const displayScore = score; const displayBest = Math.max(score, bestScore); useEffect(() => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true, damping: 22, stiffness: 200, mass: 0.8, }).start(); }, []); function handleExit() { Animated.timing(slideAnim, { toValue: 500, duration: 220, useNativeDriver: true, easing: Easing.in(Easing.cubic), }).start(() => onExit()); } async function submitRating() { setSaving(true); try { await apiFetch('/api/games/rating', { method: 'POST', body: { gameName: gameName.toLowerCase(), stars: rating, feedback: feedback.trim() || null, score, }, }); setSaved(true); } catch { // endpoint not yet live — silent } finally { setSaving(false); } } async function fetchShareText() { const data = await apiFetch<{ text: string }>('/api/games/share-text', { method: 'POST', body: { gameName: gameName.toLowerCase(), score, scoreLabel, bestScore, isNewRecord: score > bestScore, mode: 'game', }, }); return data.text || `${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`; } async function openShareSection() { setShareTextLoading(true); setShareSectionOpen(true); try { const text = await fetchShareText(); lyraShareTextRef.current = text; setShareText(text); } catch { const fallback = `${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`; lyraShareTextRef.current = fallback; setShareText(fallback); } finally { setShareTextLoading(false); } } function regenerateShareText() { if (regenLoading || shareTextLoading) return; const doRegen = async () => { setRegenLoading(true); try { const text = await fetchShareText(); lyraShareTextRef.current = text; setShareText(text); } catch { // keep existing text on error } finally { setRegenLoading(false); } }; const userModified = shareText.trim() !== lyraShareTextRef.current.trim(); if (userModified) { Alert.alert( t('gameOver.regen_confirm_title'), t('gameOver.regen_confirm_body'), [ { text: t('common.cancel'), style: 'cancel' }, { text: t('gameOver.regen_confirm_ok'), onPress: doRegen }, ] ); } else { doRegen(); } } async function submitCommunityPost() { if (!shareText.trim()) return; setSharing(true); setPostError(false); try { const scoreLine = `${scoreLabel ?? 'Score'}: ${score}`; await apiFetch('/api/community/post', { method: 'POST', body: { category: 'game_share', content: `${gameName}\n${scoreLine}\n${shareText.trim()}`, }, }); setPosted(true); setShareSectionOpen(false); setTimeout(() => handleExit(), 1500); } catch (err) { console.error('[gameover/post] failed:', err); setPostError(true); } finally { setSharing(false); } } const pillBg = colors.surfaceElevated; const pillText = colors.text; const pillMuted = colors.textMuted; return ( {/* Grab-handle */} {/* Scrollable body */} {/* Lyra avatar + message */} Lyra {msg.title} {msg.body} {/* Score pills */} {displayScore} {scoreLabel ?? t('gameOver.score')} {displayBest} {isNewBest ? t('gameOver.newBest') : t('gameOver.best')} {/* Star rating */} { if (!saved) setRating(v); }} /> {saved ? ( {t('gameOver.rating_saved')} ) : null} {/* Feedback textarea + save */} {rating > 0 && !saved ? (