import { useMemo, useRef, useState } from 'react' import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native' import { ArrowUp, Check, CircleHelp } from 'lucide-react-native' import { colors, radii, spacing, typography } from '../theme/mobile-theme' import { formatQuestionAnswer, type MobileChatQuestion } from './mobile-native-chat-question' type Props = { question: MobileChatQuestion onAnswer: (text: string) => Promise } /** Renders an agent's choice prompt as a tappable card. Single-select answers * on tap; multi-select toggles then Submits; an always-present text entry lets * the user answer freely (the escape hatch) when the heuristic misreads the * options or none apply. */ export function MobileNativeChatQuestion({ question, onAnswer }: Props): React.JSX.Element { const [selected, setSelected] = useState([]) const [freeText, setFreeText] = useState('') const [sending, setSending] = useState(false) const sendingRef = useRef(false) const hasOptions = question.options.length > 0 const trimmedFreeText = freeText.trim() const toggle = (option: string): void => { setSelected((prev) => prev.includes(option) ? prev.filter((o) => o !== option) : [...prev, option] ) } const sendAnswer = async (text: string): Promise => { if (sendingRef.current) { return false } sendingRef.current = true setSending(true) try { return await onAnswer(text) } finally { sendingRef.current = false setSending(false) } } const answerSingle = async (option: string): Promise => { await sendAnswer(formatQuestionAnswer(question, [option])) } const submitMulti = async (): Promise => { if (selected.length === 0) { return } await sendAnswer(formatQuestionAnswer(question, selected)) } const submitFreeText = async (): Promise => { if (trimmedFreeText.length === 0) { return } // Free text is an unknown entry; formatQuestionAnswer passes it through. if (await sendAnswer(formatQuestionAnswer(question, [trimmedFreeText]))) { setFreeText('') } } const canSubmitMulti = selected.length > 0 && !sending const canSendFreeText = trimmedFreeText.length > 0 && !sending // Stable keys for option rows even if an agent repeats a label. const optionRows = useMemo( () => question.options.map((label, index) => ({ label, key: `${index}:${label}` })), [question.options] ) return ( {question.question} {hasOptions ? ( {optionRows.map(({ label, key }) => { const isSelected = selected.includes(label) return ( [ styles.option, isSelected && styles.optionSelected, pressed && styles.pressed ]} onPress={() => (question.multiSelect ? toggle(label) : answerSingle(label))} > {question.multiSelect ? ( {isSelected ? : null} ) : null} {label} ) })} ) : null} {question.multiSelect && hasOptions ? ( [ styles.submit, !canSubmitMulti && styles.submitDisabled, pressed && canSubmitMulti && styles.pressed ]} onPress={submitMulti} disabled={!canSubmitMulti} > Submit{selected.length > 0 ? ` (${selected.length})` : ''} ) : null} [ styles.freeSend, !canSendFreeText && styles.freeSendDisabled, pressed && canSendFreeText && styles.pressed ]} onPress={submitFreeText} disabled={!canSendFreeText} > ) } const styles = StyleSheet.create({ card: { marginHorizontal: spacing.lg, marginVertical: spacing.sm, padding: spacing.md, gap: spacing.sm, backgroundColor: colors.bgPanel, borderRadius: radii.card, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.borderSubtle }, header: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm }, question: { flex: 1, color: colors.textPrimary, fontSize: typography.bodySize + 1, fontWeight: '600', lineHeight: typography.bodySize + 7 }, options: { gap: spacing.xs }, option: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, minHeight: 44, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, backgroundColor: colors.bgRaised, borderRadius: radii.button, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.borderSubtle }, optionSelected: { borderColor: colors.accentBlue }, optionText: { flex: 1, color: colors.textPrimary, fontSize: typography.bodySize + 1 }, checkbox: { width: 20, height: 20, borderRadius: radii.button, borderWidth: 1.5, borderColor: colors.textMuted, alignItems: 'center', justifyContent: 'center' }, checkboxOn: { backgroundColor: colors.accentBlue, borderColor: colors.accentBlue }, submit: { minHeight: 44, alignItems: 'center', justifyContent: 'center', borderRadius: radii.button, backgroundColor: colors.accentBlue }, submitDisabled: { backgroundColor: colors.bgRaised }, submitText: { color: colors.onMergeGreen, fontSize: typography.bodySize + 1, fontWeight: '600' }, submitTextDisabled: { color: colors.textMuted }, freeTextRow: { flexDirection: 'row', alignItems: 'flex-end', gap: spacing.sm }, freeInput: { flex: 1, minHeight: 40, maxHeight: 120, color: colors.textPrimary, fontSize: typography.bodySize + 1, backgroundColor: colors.bgRaised, borderRadius: radii.input, paddingHorizontal: spacing.md, paddingTop: spacing.sm, paddingBottom: spacing.sm }, freeSend: { width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.textPrimary }, freeSendDisabled: { backgroundColor: colors.bgRaised }, pressed: { opacity: 0.7 } })