import React, { useMemo, useState } from 'react'
import { Platform, View } from 'react-native'
import { responsiveHeight } from 'react-native-responsive-dimensions'
import { background } from '../../assets'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from '../../data/data'
import Page from '../../layouts/Page'
import { goBack } from '../../navigation/NavigationService'
import { useUser } from '../../providers/UserDataProvider'
import { gutters } from '../../styles'
import { sanitizeStructureList } from '../../utils/songStructure'
import CreatingLyrics from './CreatingLyrics'
import CustomizeSongStructure from './CustomizeSongStructure'
import EmotionConvey from './EmotionConvey'
import Goals from './Goals'
import Rhymes from './Rhymes'
import SongStructure from './SongStructure'
import SongStyle from './SongStyle'
import SongTo from './SongTo'
import SpecificityContext from './SpecificityContext'
const MAX_STEP_INDEX = 8
const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false }
const CreateLyricsWithAi = () => {
const { selectedProject } = useUser()
const [selectedIndex, setSelectedIndex] = useState(0)
const handleLyricsError = React.useCallback(() => {
setSelectedIndex(7)
}, [setSelectedIndex])
// Collected state across steps
const [objective, setObjective] = useState(null) // from Goals list
const [otherObjective, setOtherObjective] = useState('')
const [context, setContext] = useState('')
const [emotion, setEmotion] = useState(null)
const [style, setStyle] = useState(null) // from list
const [otherStyle, setOtherStyle] = useState('')
const [audience, setAudience] = useState('')
const [structure, setStructure] = useState(null) // selected structure string
const [rhymes, setRhymes] = useState(null)
const [customStructure, setCustomStructure] = useState(null) // array like ['couplet','refrain']
const trimmedOtherObjective = typeof otherObjective === 'string' ? otherObjective.trim() : ''
const shouldUseOtherObjective = objective === OTHER_OBJECTIVE_OPTION
const persistedOtherObjective = shouldUseOtherObjective ? trimmedOtherObjective : ''
const trimmedOtherStyle = typeof otherStyle === 'string' ? otherStyle.trim() : ''
const shouldUseOtherStyle = style === OTHER_STYLE_OPTION
const persistedOtherStyle = shouldUseOtherStyle ? trimmedOtherStyle : ''
// Pré-remplir les états depuis le projet sélectionné si disponibles
React.useEffect(() => {
if (!selectedProject) return
const sel = selectedProject?.selections || {}
const cfg = selectedProject?.config || {}
const rawOtherObjective = typeof sel.otherObjective === 'string' ? sel.otherObjective : ''
const trimmedSavedOtherObjective = rawOtherObjective.trim()
const hasSavedOtherObjective = trimmedSavedOtherObjective.length > 0
const savedObjective =
typeof sel.objective === 'string' && sel.objective.trim().length > 0
? sel.objective.trim()
: null
if (!otherObjective && hasSavedOtherObjective) {
setOtherObjective(rawOtherObjective)
}
const shouldSelectOtherObjective =
hasSavedOtherObjective && (!savedObjective || savedObjective === OTHER_OBJECTIVE_OPTION)
const resolvedSavedObjective = shouldSelectOtherObjective
? OTHER_OBJECTIVE_OPTION
: savedObjective
if (resolvedSavedObjective && objective !== resolvedSavedObjective) {
setObjective(resolvedSavedObjective)
} else if (!resolvedSavedObjective && objective !== null) {
setObjective(null)
}
if (!context && typeof sel.context === 'string') setContext(sel.context)
// Emotion: accepter objet {title, description} ou string "Titre : description"
if (emotion == null && sel.emotion) {
if (typeof sel.emotion === 'object' && sel.emotion.title) {
setEmotion({
title: sel.emotion.title,
description: sel.emotion.description || '',
})
} else if (typeof sel.emotion === 'string') {
const [t, d] = sel.emotion.split(':')
const title = (t || '').trim()
const description = (d || '').trim()
if (title) setEmotion({ title, description })
}
}
const rawOtherStyle = typeof sel.otherStyle === 'string' ? sel.otherStyle : ''
const trimmedSavedOtherStyle = rawOtherStyle.trim()
const hasSavedOtherStyle = trimmedSavedOtherStyle.length > 0
const savedStyle =
typeof sel.style === 'string' && sel.style.trim().length > 0 ? sel.style.trim() : null
if (!otherStyle && hasSavedOtherStyle) {
setOtherStyle(rawOtherStyle)
}
const shouldSelectOtherStyle =
hasSavedOtherStyle && (!savedStyle || savedStyle === OTHER_STYLE_OPTION)
const resolvedSavedStyle = shouldSelectOtherStyle ? OTHER_STYLE_OPTION : savedStyle
if (resolvedSavedStyle && style !== resolvedSavedStyle) {
setStyle(resolvedSavedStyle)
} else if (!resolvedSavedStyle && style !== null) {
setStyle(null)
}
if (!audience && typeof sel.audience === 'string') setAudience(sel.audience)
// Structure choisie (string) si déjà enregistrée dans selections
if (structure == null && typeof sel.structure === 'string' && sel.structure)
setStructure(sel.structure)
// Rimes
if (rhymes == null && typeof sel.rhymes === 'string' && sel.rhymes) setRhymes(sel.rhymes)
// Structure personnalisée: prioriser selections.customStructure puis config.structure
const savedCustom = Array.isArray(sel.customStructure)
? sanitizeStructureList(sel.customStructure, CUSTOM_SANITIZE_OPTIONS)
: null
const cfgStructure = Array.isArray(cfg.structure)
? sanitizeStructureList(cfg.structure, CUSTOM_SANITIZE_OPTIONS)
: null
const fallbackStructure = savedCustom && savedCustom.length ? savedCustom : cfgStructure
if (!Array.isArray(customStructure) && fallbackStructure?.length) {
setCustomStructure(fallbackStructure)
} else if (
Array.isArray(customStructure) &&
fallbackStructure?.length &&
(customStructure.length !== fallbackStructure.length ||
customStructure.some((value, idx) => value !== fallbackStructure[idx]))
) {
setCustomStructure(fallbackStructure)
}
}, [selectedProject])
React.useEffect(() => {
console.debug('[CreateLyricsWithAi.web] structure state', {
structure,
customStructure,
})
}, [structure, customStructure])
const parsedStructure = useMemo(() => {
// Parses strings like "1 couplet, 1 refrain, 1 couplet, 1 refrain"
try {
if (!structure || typeof structure !== 'string') return null
const parts = structure.split(',')
const result = []
parts.forEach((seg) => {
const s = seg.trim().toLowerCase()
const coupletMatch = s.match(/(\d+)\s+couplet/)
const refrainMatch = s.match(/(\d+)\s+refrain/)
if (coupletMatch) {
const count = parseInt(coupletMatch[1], 10)
for (let i = 0; i < count; i++) result.push('couplet')
}
if (refrainMatch) {
const count = parseInt(refrainMatch[1], 10)
for (let i = 0; i < count; i++) result.push('refrain')
}
})
const sanitizedResult = sanitizeStructureList(result)
return sanitizedResult.length ? sanitizedResult : null
} catch (e) {
return null
}
}, [structure])
const sanitizedCustomStructure = useMemo(() => {
if (!Array.isArray(customStructure) || customStructure.length === 0) {
return []
}
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS)
}, [customStructure])
const sanitizedParsedStructure = useMemo(() => {
if (!Array.isArray(parsedStructure) || parsedStructure.length === 0) {
return []
}
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS)
}, [parsedStructure])
const fallbackProjectStructure = useMemo(() => {
const raw = selectedProject?.config?.structure
if (!Array.isArray(raw) || raw.length === 0) return []
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS)
}, [selectedProject])
const baseStructureForCustomizer = useMemo(() => {
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure
return fallbackProjectStructure
}, [sanitizedCustomStructure, sanitizedParsedStructure, fallbackProjectStructure])
const progress = useMemo(() => 16 + selectedIndex * 9, [selectedIndex])
const lyricsConfig = useMemo(() => {
const resolvedObjective = shouldUseOtherObjective
? persistedOtherObjective || undefined
: objective || undefined
const resolvedStyle = shouldUseOtherStyle
? persistedOtherStyle || undefined
: style || undefined
const sanitizedCustom = sanitizedCustomStructure
const sanitizedParsed = sanitizedParsedStructure
return {
objective: resolvedObjective,
context: context?.trim() ? context.trim() : undefined,
emotion:
emotion?.title && emotion?.description
? `${emotion.title} : ${emotion.description}`
: undefined,
style: resolvedStyle,
audience: audience?.trim() ? audience.trim() : undefined,
structure:
sanitizedCustom.length > 0
? sanitizedCustom
: sanitizedParsed.length > 0
? sanitizedParsed
: undefined,
rhymes: rhymes || undefined,
}
}, [
shouldUseOtherObjective,
persistedOtherObjective,
objective,
shouldUseOtherStyle,
persistedOtherStyle,
context,
emotion,
style,
audience,
sanitizedParsedStructure,
rhymes,
sanitizedCustomStructure,
])
const isNextDisabled = React.useMemo(() => {
switch (selectedIndex) {
case 0: {
const hasObjective = typeof objective === 'string' && objective.length > 0
const hasOther = trimmedOtherObjective.length > 0
return !(hasObjective || hasOther)
}
case 1: {
const hasContext = typeof context === 'string' && context.trim().length > 0
return !hasContext
}
case 2: {
const hasEmotion =
(emotion && typeof emotion === 'object' && !!emotion.title) ||
(typeof emotion === 'string' && emotion.trim().length > 0)
return !hasEmotion
}
case 3: {
const hasStyle = typeof style === 'string' && style.length > 0
const hasOther = trimmedOtherStyle.length > 0
return !(hasStyle || hasOther)
}
case 4: {
const hasAudience = typeof audience === 'string' && audience.trim().length > 0
return !hasAudience
}
case 5: {
const hasStructure = typeof structure === 'string' && structure.length > 0
return !hasStructure
}
case 7: {
const hasRhymes = typeof rhymes === 'string' && rhymes.length > 0
return !hasRhymes
}
default:
return false
}
}, [
selectedIndex,
objective,
trimmedOtherObjective,
context,
emotion,
style,
trimmedOtherStyle,
audience,
structure,
rhymes,
])
const onPressNext = async () => {
setSelectedIndex((idx) => Math.min(idx + 1, MAX_STEP_INDEX))
}
const onPressBack = () => {
if (selectedIndex > 0) {
setSelectedIndex((idx) => Math.max(0, idx - 1))
} else {
goBack()
}
}
const steps = [
{
key: 'goals',
render: () => (
),
},
{
key: 'context',
render: () => ,
},
{
key: 'emotion',
render: () => ,
},
{
key: 'style',
render: () => (
),
},
{
key: 'audience',
render: () => ,
},
{
key: 'structure',
render: () => ,
},
{
key: 'customStructure',
render: () => (
),
},
{
key: 'rhymes',
render: () => ,
},
{
key: 'creating',
render: () => (
),
},
]
const currentStep = steps[selectedIndex]
return (
{currentStep ? (
{currentStep.render()}
) : null}
{selectedIndex !== 8 && (
)}
)
}
export default CreateLyricsWithAi