import { useState, useCallback, useRef } from 'react' import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Animated, ScrollView, } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import { useLocalSearchParams, useRouter } from 'expo-router' import { ArrowLeft, GraduationCap } from 'lucide-react-native' import { C } from '@/lib/theme' import { apiFetch } from '@/lib/api' import { ENDPOINTS } from '@/lib/config' import { useFocusEffect } from 'expo-router' interface Card { id: string front: string back: string interval: number type?: string } type SessionState = 'loading' | 'reviewing' | 'done' | 'error' const GRADE_LABELS: { grade: 1 | 2 | 3 | 4; label: string; color: string; bg: string; border: string }[] = [ { grade: 1, label: 'Oublié', color: '#dc2626', bg: 'rgba(239,68,68,0.10)', border: 'rgba(239,68,68,0.30)' }, { grade: 2, label: 'Difficile',color: '#b45309', bg: 'rgba(245,158,11,0.10)', border: 'rgba(245,158,11,0.30)' }, { grade: 3, label: 'Bien', color: '#047857', bg: 'rgba(16,185,129,0.10)', border: 'rgba(16,185,129,0.30)' }, { grade: 4, label: 'Parfait', color: '#A47148', bg: 'rgba(164,113,72,0.10)', border: 'rgba(164,113,72,0.30)' }, ] export default function SessionScreen() { const router = useRouter() const params = useLocalSearchParams<{ deckId: string; deckName: string }>() const { deckId, deckName } = params const [state, setState] = useState('loading') const [cards, setCards] = useState([]) const [index, setIndex] = useState(0) const [flipped, setFlipped] = useState(false) const [reviewed, setReviewed] = useState(0) const [errorMsg, setErrorMsg] = useState(null) // Animation flip const flipAnim = useRef(new Animated.Value(0)).current const frontInterp = flipAnim.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '180deg'] }) const backInterp = flipAnim.interpolate({ inputRange: [0, 1], outputRange: ['180deg', '360deg'] }) const loadSession = useCallback(async () => { if (!deckId) return setState('loading') setIndex(0) setFlipped(false) setReviewed(0) flipAnim.setValue(0) try { const res = await apiFetch(ENDPOINTS.flashcardSession(deckId)) const data = await res.json() if (!res.ok) throw new Error(data.error ?? `Erreur ${res.status}`) setCards(data.cards ?? []) setState(data.cards?.length === 0 ? 'done' : 'reviewing') } catch (e: any) { setErrorMsg(e.message ?? 'Erreur') setState('error') } }, [deckId]) useFocusEffect(useCallback(() => { loadSession() }, [loadSession])) const flip = () => { if (flipped) return setFlipped(true) Animated.spring(flipAnim, { toValue: 1, useNativeDriver: true, friction: 8 }).start() } const grade = async (g: 1 | 2 | 3 | 4) => { const card = cards[index] if (!card) return try { await apiFetch(ENDPOINTS.flashcardReview, { method: 'POST', body: JSON.stringify({ cardId: card.id, grade: g }), }) } catch { /* silencieux — on avance quand même */ } const next = index + 1 setReviewed((r) => r + 1) if (next >= cards.length) { setState('done') } else { setIndex(next) setFlipped(false) Animated.timing(flipAnim, { toValue: 0, duration: 0, useNativeDriver: true }).start() } } const current = cards[index] const progress = cards.length > 0 ? index / cards.length : 0 return ( {/* Header */} router.back()} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}> {deckName ?? 'Révision'} {state === 'reviewing' && ( {index + 1}/{cards.length} )} {/* Barre de progression */} {state === 'reviewing' && ( )} {/* Contenu */} {state === 'loading' && ( )} {state === 'error' && ( {errorMsg} Réessayer )} {state === 'done' && ( Session terminée ! {reviewed > 0 ? `${reviewed} carte${reviewed > 1 ? 's' : ''} révisée${reviewed > 1 ? 's' : ''}` : 'Tout est à jour 🎉'} router.back()} style={s.doneBtn}> Retour aux paquets )} {state === 'reviewing' && current && ( {/* Carte flip */} {/* Face avant */} Question {current.front} {!flipped && ( Appuyez pour révéler la réponse )} {/* Face arrière */} Réponse {current.back} {/* Boutons de note (visibles seulement après flip) */} {flipped && ( {GRADE_LABELS.map(({ grade: g, label, color, bg, border }) => ( grade(g)} activeOpacity={0.8} > {g} {label} ))} )} )} ) } const s = StyleSheet.create({ safe: { flex: 1, backgroundColor: C.paper }, header: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 20, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: C.border, }, headerTitle: { flex: 1, fontSize: 17, fontWeight: '700', color: C.ink }, counter: { fontSize: 13, color: C.concrete, fontWeight: '600' }, progressBarWrap: { height: 3, backgroundColor: C.border }, progressBarFill: { height: 3, backgroundColor: C.brand }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 }, sessionWrap: { flex: 1, padding: 20, justifyContent: 'space-between' }, cardWrap: { flex: 1, marginBottom: 16 }, card: { position: 'absolute', inset: 0, borderRadius: 18, borderWidth: 1, borderColor: C.border, backgroundColor: C.surface, backfaceVisibility: 'hidden', padding: 24, }, cardFront: { zIndex: 1 }, cardBack: { backgroundColor: '#f0f7ff' }, cardContent: { flexGrow: 1, justifyContent: 'center', alignItems: 'center', paddingBottom: 48 }, cardLabel: { fontSize: 11, fontWeight: '700', color: C.brand, letterSpacing: 1, textTransform: 'uppercase', marginBottom: 16 }, cardText: { fontSize: 20, color: C.ink, textAlign: 'center', lineHeight: 30, fontWeight: '500' }, tapHint: { position: 'absolute', bottom: 20, left: 0, right: 0, alignItems: 'center' }, tapTxt: { fontSize: 12, color: C.concrete, fontStyle: 'italic' }, gradeRow: { flexDirection: 'row', gap: 8 }, gradeBtn: { flex: 1, alignItems: 'center', paddingVertical: 12, borderRadius: 14, borderWidth: 1.5, }, gradeNum: { fontSize: 18, fontWeight: '800' }, gradeLbl: { fontSize: 10, fontWeight: '600', marginTop: 2 }, doneTitle: { fontSize: 24, fontWeight: '800', color: C.ink, marginTop: 20, marginBottom: 8 }, doneSub: { fontSize: 15, color: C.concrete, marginBottom: 32 }, doneBtn: { backgroundColor: C.brand, paddingHorizontal: 28, paddingVertical: 14, borderRadius: 14 }, doneBtnTxt: { color: '#fff', fontWeight: '700', fontSize: 16 }, errorTxt: { fontSize: 15, color: '#e11d48', textAlign: 'center', marginBottom: 12 }, retryBtn: { backgroundColor: C.brand, paddingHorizontal: 20, paddingVertical: 10, borderRadius: 10 }, retryTxt: { color: '#fff', fontWeight: '600' }, })