import { BlurView } from "expo-blur"; import React, { useEffect, useRef, useState } from "react"; import { Platform, Text, View } from "react-native"; import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; import alert from "../../components/Alert"; import GradientButton from "../../components/GradientButton"; import ProgressBar from "../../components/ProgressBar"; import firebase from "../../config/firebase"; import { Routes } from "../../navigation"; import { navigate } from "../../navigation/NavigationService"; import { useUser } from "../../providers/UserDataProvider"; import { Palette, Style } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; import { normalizeStructureType, sanitizeStructureList, segmentRequiresLyrics, } from "../../utils/songStructure"; const FAKE_PROGRESS_MAX = 96; const PROGRESS_INTERVAL_MS = 250; const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false }; const CreatingLyrics = ({ active, config, selections }) => { const [progress, setProgress] = useState(0); const [called, setCalled] = useState(false); const [result, setResult] = useState(null); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); const hasShownErrorAlert = useRef(false); const { setIsLoading } = useMinuit(); const { updateProjectData } = useUser(); const progressValue = Math.max(0, Math.min(100, Math.round(progress))); // Reset when becomes active useEffect(() => { if (active) { setCalled(false); setResult(null); setProgress(0); setSaved(false); setError(null); hasShownErrorAlert.current = false; } }, [active]); useEffect(() => { if (!active) return undefined; const interval = setInterval(() => { setProgress((prevProgress) => { if (result || error) return prevProgress; if (prevProgress >= FAKE_PROGRESS_MAX) { return FAKE_PROGRESS_MAX; } const increment = prevProgress < 60 ? 1 : prevProgress < 80 ? 0.6 : 0.3; const next = prevProgress + increment; return next >= FAKE_PROGRESS_MAX ? FAKE_PROGRESS_MAX : next; }); }, PROGRESS_INTERVAL_MS); return () => clearInterval(interval); }, [active, result, error]); useEffect(() => { const run = async () => { try { setCalled(true); console.log("🚀 [CreatingLyrics] Lancement de la génération", { platform: Platform.OS, }); const sanitizedStructure = sanitizeStructureList( config?.structure, CUSTOM_SANITIZE_OPTIONS, ); const callable = firebase .functions() .httpsCallable("lyrics-generateLyrics"); const { data } = await callable({ objective: config?.objective, context: config?.context, emotion: config?.emotion, style: config?.style, audience: config?.audience, structure: sanitizedStructure, rhymes: config?.rhymes, }); const rawLyrics = Array.isArray(data?.lyrics) ? data.lyrics : []; const normalizedLyrics = rawLyrics.map((section) => { const sanitizedType = normalizeStructureType(section?.type); return { ...section, type: sanitizedType, lyrics: section?.lyrics || "", }; }); const remaining = [...normalizedLyrics]; const takeForType = (type, { fallback = true } = {}) => { const index = remaining.findIndex((item) => item.type === type); if (index !== -1) { return remaining.splice(index, 1)[0]; } if (!fallback) return null; return remaining.shift() || null; }; const structuredLyrics = sanitizedStructure.length ? sanitizedStructure.map((rawType) => { const type = normalizeStructureType(rawType); if (!segmentRequiresLyrics(type)) { const match = takeForType(type, { fallback: false }); return { type, lyrics: match?.lyrics || "" }; } const match = takeForType(type, { fallback: true }); return { type, lyrics: match?.lyrics || "", }; }) : normalizedLyrics; const alignedLyrics = structuredLyrics.concat(remaining); const processedResult = { ...data, lyrics: alignedLyrics, structure: sanitizedStructure, }; setResult(processedResult); setProgress(100); console.log("✨ [CreatingLyrics] Génération réussie"); } catch (e) { console.error("🔥 [CreatingLyrics] Erreur de génération", e); const rawMessage = typeof e?.message === "string" ? e.message : ""; const cleanedMessage = rawMessage.replace( /^functions error: \w+-\w+:\s*/i, "", ); setError({ message: cleanedMessage?.length > 0 ? cleanedMessage : "Une erreur est survenue lors de la génération des paroles.", code: e?.code, details: e?.details, }); } finally { await setIsLoading(false); } }; if (active && !called) { run(); } }, [active, called, config, setIsLoading]); // Lorsque la génération est terminée, enregistrer et naviguer automatiquement vers Lyrics useEffect(() => { const autoSaveAndGo = async () => { try { if (saved) return; setSaved(true); await setIsLoading(true); console.log("💾 [CreatingLyrics] Sauvegarde automatique des paroles"); const sanitizedStructure = sanitizeStructureList( result?.structure || config?.structure, CUSTOM_SANITIZE_OPTIONS, ); const baseConfig = config && typeof config === "object" && !Array.isArray(config) ? { ...config } : {}; if (sanitizedStructure.length > 0) { baseConfig.structure = sanitizedStructure; } else if ( Object.prototype.hasOwnProperty.call(baseConfig, "structure") ) { delete baseConfig.structure; } const persistedConfig = Object.keys(baseConfig).length > 0 ? baseConfig : null; let persistedSelections = selections && typeof selections === "object" && !Array.isArray(selections) ? { ...selections } : null; if (persistedSelections) { if ("customStructure" in persistedSelections) { persistedSelections.customStructure = sanitizeStructureList( persistedSelections.customStructure, CUSTOM_SANITIZE_OPTIONS, ); } if ("parsedStructure" in persistedSelections) { persistedSelections.parsedStructure = sanitizeStructureList( persistedSelections.parsedStructure, ); } } await updateProjectData({ title: result?.title || "", lyrics: Array.isArray(result?.lyrics) ? result.lyrics : [], description: result?.lyricsDescription, config: persistedConfig, selections: persistedSelections, hasLyrics: false, }); navigate(Routes.Lyrics); } catch (e) { console.error("⚠️ [CreatingLyrics] Erreur lors de la sauvegarde", e); } finally { await setIsLoading(false); } }; if (active && result && progress >= 100 && !saved && !error) { autoSaveAndGo(); } }, [ active, result, progress, saved, error, setIsLoading, updateProjectData, config, selections, ]); useEffect(() => { if (error && !hasShownErrorAlert.current) { hasShownErrorAlert.current = true; const redirect = () => { console.log("↩️ [CreatingLyrics] Retour à WritingLyrics après erreur"); navigate(Routes.WritingLyrics); }; alert( "Brief à reformuler", __DEV__ ? error.message : "Une erreur s'est produite", [ { text: "OK", onPress: redirect, }, ], { cancelable: false }, ); if (Platform.OS === "web") { redirect(); } } }, [error]); return ( {error ? "Impossible de générer les paroles" : "Ton texte est\nen cours de création"} {progressValue}% {!saved && result && !error && ( { try { if (saved) return; setSaved(true); await setIsLoading(true); console.log( "📄 [CreatingLyrics] Consultation manuelle du texte", ); await updateProjectData({ title: result?.title || "", lyrics: Array.isArray(result?.lyrics) ? result.lyrics : [], config: config || null, selections: selections || null, hasLyrics: false, }); navigate(Routes.Lyrics); } catch (e) { console.error( "⚠️ [CreatingLyrics] Erreur lors de l'ouverture manuelle", e, ); } finally { await setIsLoading(false); } }} /> )} ); }; export default CreatingLyrics;