'use client' import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { useSession } from 'next-auth/react' import { motion, AnimatePresence } from 'motion/react' import { Zap, History, Plus, Wind, FileText, ChevronRight, UserPlus, Activity, Download, Share2, Check, Users, Globe, Lock, Menu, Star, Sparkles, List, LayoutGrid, X, Edit2, } from 'lucide-react' import dynamic from 'next/dynamic' import { useLanguage } from '@/lib/i18n' import { useBrainstormSession, useBrainstormSessions, useCreateBrainstorm, useExpandIdea, useDismissIdea, useConvertIdea, useExportBrainstorm, useFinalizeBrainstorm, useDeleteBrainstorm, useJoinBrainstorm, useAddManualIdea, useBrainstormActivity, useUpdateBrainstormSettings, useStarIdea, useSummarizeBrainstorm, useRenameBrainstormSession, } from '@/hooks/use-brainstorm' import { useBrainstormSocket } from '@/hooks/use-brainstorm-socket' import { LiveCursors, PresenceAvatars, useCursorTracking } from '@/components/brainstorm/live-cursors' import { ActivityFeed } from '@/components/brainstorm/activity-feed' import { BrainstormShareDialog } from '@/components/brainstorm/brainstorm-share-dialog' import { GhostCursor } from '@/components/brainstorm/ghost-cursor' import { PlaybackBar } from '@/components/brainstorm/playback-bar' import type { BrainstormIdea, BrainstormNoteRef, BrainstormActivityItem } from '@/types/brainstorm' const WaveCanvas = dynamic( () => import('@/components/brainstorm/wave-canvas').then((m) => ({ default: m.WaveCanvas, })), { ssr: false } ) function CursorTrackerEffect({ containerRef, moveCursor }: { containerRef: React.RefObject; moveCursor: (c: { x: number; y: number } | null) => void }) { useCursorTracking(containerRef, moveCursor) return null } const WAVE_COLORS: Record = { 1: { border: 'border-orange-200', bg: 'bg-orange-50', text: 'text-orange-600' }, 2: { border: 'border-brand-accent', bg: 'bg-brand-accent', text: 'text-brand-accent' }, 3: { border: 'border-violet-200', bg: 'bg-violet-50', text: 'text-violet-600' }, } export function BrainstormPage() { const router = useRouter() const searchParams = useSearchParams() const { t, language } = useLanguage() const { data: authSession } = useSession() const urlSessionId = searchParams.get('session') const urlSeed = searchParams.get('seed') const urlSourceNoteId = searchParams.get('sourceNoteId') const urlInviteToken = searchParams.get('invite') const [seedInput, setSeedInput] = useState('') const [selectedIdeaId, setSelectedIdeaId] = useState(null) const [activeSessionId, setActiveSessionId] = useState(urlSessionId) const [autoStarted, setAutoStarted] = useState(false) useEffect(() => { if (urlSessionId && urlSessionId !== activeSessionId) { setActiveSessionId(urlSessionId) } }, [urlSessionId]) const [showActivityFeed, setShowActivityFeed] = useState(false) const [showShareDialog, setShowShareDialog] = useState(false) const [manualEditCount, setManualEditCount] = useState(0) const [shareStatus, setShareStatus] = useState<'idle' | 'copying' | 'copied'>('idle') const { data: sessions, isLoading: sessionsLoading } = useBrainstormSessions() const { data: sessionResult, isLoading: sessionLoading } = useBrainstormSession(activeSessionId) const session = sessionResult?.session || null const sessionMeta = sessionResult?.meta const isGuest = sessionMeta?.role === 'guest' const canEdit = sessionMeta?.canEdit ?? true const updateSettings = useUpdateBrainstormSettings(activeSessionId || '') const createBrainstorm = useCreateBrainstorm() const expandIdea = useExpandIdea(activeSessionId || '') const dismissIdea = useDismissIdea(activeSessionId || '') const convertIdea = useConvertIdea(activeSessionId || '') const exportBrainstorm = useExportBrainstorm(activeSessionId || '') const finalizeBrainstorm = useFinalizeBrainstorm(activeSessionId || '') const deleteBrainstorm = useDeleteBrainstorm() const joinMutation = useJoinBrainstorm() const addManualIdea = useAddManualIdea(activeSessionId || '') const { data: activities } = useBrainstormActivity(activeSessionId) const starIdea = useStarIdea(activeSessionId || '') const summarize = useSummarizeBrainstorm(activeSessionId || '') const renameSession = useRenameBrainstormSession(activeSessionId || '') const [impactToast, setImpactToast] = useState<{ notesEnriched: number; notesMarkedDry: number } | null>(null) const [exportError, setExportError] = useState(null) const [exportToast, setExportToast] = useState<{ noteTitle: string; notebookName: string } | null>(null) const [convertToast, setConvertToast] = useState<{ noteTitle: string; noteId: string } | null>(null) const [remoteMove, setRemoteMove] = useState<{ ideaId: string; x: number; y: number; _seq: number } | null>(null) const [playbackIdeas, setPlaybackIdeas] = useState(null) const [viewMode, setViewMode] = useState<'canvas' | 'list'>('canvas') const [summaryOpen, setSummaryOpen] = useState(false) const [summaryText, setSummaryText] = useState(null) const [renamingSession, setRenamingSession] = useState(null) const [renameInput, setRenameInput] = useState('') const canvasContainerRef = useRef(null) const moveSeq = useRef(0) const { others: socketOthers, moveCursor, activities: socketActivities, socketRef, aiProcessingNodeId } = useBrainstormSocket( activeSessionId, authSession?.user?.id || null, authSession?.user?.name || null, useCallback((data: { ideaId: string; positionX: number; positionY: number }) => { moveSeq.current++ setRemoteMove({ ideaId: data.ideaId, x: data.positionX, y: data.positionY, _seq: moveSeq.current }) }, []) ) const mergedActivities: BrainstormActivityItem[] = useMemo(() => { const restActivities: BrainstormActivityItem[] = (activities || []) as BrainstormActivityItem[] const socketActs = (socketActivities || []).map((a: any) => ({ id: `${a.userId}-${a.action}-${Date.now()}-${Math.random()}`, action: a.action, details: a.details, createdAt: new Date().toISOString(), user: { name: a.userName || null, image: null }, })) const seen = new Set() const merged = [...socketActs, ...restActivities] return merged.filter((a) => { const key = `${a.action}-${a.user?.name}-${a.details?.ideaTitle || ''}` if (seen.has(key)) return false seen.add(key) return true }).slice(0, 50) }, [activities, socketActivities]) const selectedIdea = useMemo(() => { if (!selectedIdeaId || !session) return null return session.ideas.find((i) => i.id === selectedIdeaId) || null }, [selectedIdeaId, session]) useEffect(() => { if (urlSeed && !autoStarted && !activeSessionId && !createBrainstorm.isPending) { setAutoStarted(true) createBrainstorm.mutateAsync({ seedIdea: urlSeed, sourceNoteId: urlSourceNoteId || undefined, locale: language, }).then((result) => { setActiveSessionId(result.session.id) router.replace('/brainstorm?session=' + result.session.id) }).catch(() => {}) } }, [urlSeed, autoStarted, activeSessionId]) useEffect(() => { if (urlInviteToken && !autoStarted) { setAutoStarted(true) joinMutation.mutateAsync(urlInviteToken).then((result) => { setActiveSessionId(result.sessionId) router.replace('/brainstorm?session=' + result.sessionId) }).catch(() => {}) } }, [urlInviteToken]) const handleStartBrainstorm = async (seed?: string) => { const input = seed || seedInput if (!input.trim()) return try { const result = await createBrainstorm.mutateAsync({ seedIdea: input.trim(), locale: language }) setActiveSessionId(result.session.id) setSeedInput('') } catch {} } const handleDelete = async (sessionId: string) => { try { await deleteBrainstorm.mutateAsync(sessionId) if (activeSessionId === sessionId) { setActiveSessionId(null) setSelectedIdeaId(null) } } catch {} } const handleDeepen = async (idea: BrainstormIdea) => { try { await expandIdea.mutateAsync({ ideaId: idea.id, locale: language }) } catch {} } const handleDismiss = async (ideaId: string) => { try { await dismissIdea.mutateAsync(ideaId) setSelectedIdeaId(null) } catch {} } const handleConvert = async (idea: BrainstormIdea) => { try { const result = await convertIdea.mutateAsync(idea.id) if (result?.id) { setConvertToast({ noteTitle: result.title || idea.title, noteId: result.id }) setTimeout(() => { setConvertToast(null) router.push(`/home?openNote=${result.id}`) }, 2000) } } catch {} } const handleExport = async () => { setExportError(null) try { const result = await exportBrainstorm.mutateAsync() if (result?.id) { const notebookName = result._notebookName || t('brainstorm.exportDefaultNotebookName') setExportToast({ noteTitle: result.title || t('brainstorm.exportDefaultNoteTitle'), notebookName }) setTimeout(() => { setExportToast(null) router.push(`/home?openNote=${result.id}`) }, 2000) return } const impact = await finalizeBrainstorm.mutateAsync() if (impact) { setImpactToast(impact) setTimeout(() => setImpactToast(null), 5000) } } catch (err: any) { setExportError(err?.message || t('brainstorm.exportFailedMessage')) setTimeout(() => setExportError(null), 4000) } } const handlePositionUpdate = async (id: string, pos: { x: number; y: number }) => { if (!activeSessionId) return socketRef.current?.emit('idea:moved', { ideaId: id, positionX: pos.x, positionY: pos.y, userId: authSession?.user?.id || '' }) try { await fetch(`/api/brainstorm/${activeSessionId}/update-position`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ ideaId: id, positionX: pos.x, positionY: pos.y }), }) } catch {} } const handleCreateIdea = useCallback(({ title, parentIdeaId }: { title: string; parentIdeaId?: string; x: number; y: number }) => { addManualIdea.mutate({ title, parentIdeaId, locale: language }) }, [addManualIdea, language]) const handleStarIdea = async (ideaId: string) => { try { await starIdea.mutateAsync(ideaId) } catch {} } const handleSummarize = async () => { setSummaryOpen(true) if (summaryText) return try { const text = await summarize.mutateAsync(language) setSummaryText(text) } catch { setSummaryText(null) } } const handleRenameSession = async (sessionId: string) => { if (!renameInput.trim() || renameInput.trim() === session?.seedIdea) { setRenamingSession(null) return } try { await renameSession.mutateAsync(renameInput.trim()) } catch {} setRenamingSession(null) } const isGenerating = createBrainstorm.isPending || expandIdea.isPending return (
{/* Hamburger mobile */}

{t('brainstorm.title')}

{t('brainstorm.subtitle') || 'Unfold dimensions of potentiality'}

{session && !isGuest && (
{t('brainstorm.liveStatus')}
{authSession?.user?.name && (
{authSession.user.name.charAt(0).toUpperCase()}
)} {socketOthers.slice(0, 3).map((user, idx) => (
{user.name.charAt(0).toUpperCase()}
))} {socketOthers.length > 3 && (
+{socketOthers.length - 3}
)}
)}
setSeedInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleStartBrainstorm()} placeholder={t('brainstorm.placeholder') || 'Enter a concept to unfold...'} className="w-full relative bg-white dark:bg-[#1A1A1A] border-2 rounded-2xl px-4 sm:px-8 py-4 sm:py-7 pr-16 sm:pr-20 outline-none transition-all text-lg sm:text-2xl font-serif italic text-foreground shadow-sm group-hover:shadow-md border-border/40 focus:border-brand-accent/40 focus:ring-4 focus:ring-brand-accent/5" />
{createBrainstorm.isPending && (
{[0.2, 0.4, 0.6].map((d, i) => ( ))}
{t('brainstorm.generating') || 'AI is harvesting seeds of thought...'}
)}
{/* Seed prompts — shown only when no session and not generating */} {!session && !createBrainstorm.isPending && !activeSessionId && ( {[ t('brainstorm.exampleSeed1') || 'Simplify my morning routine', t('brainstorm.exampleSeed2') || 'Ideas for a creative project this weekend', t('brainstorm.exampleSeed3') || 'Systems to manage my energy better', t('brainstorm.exampleSeed4') || 'What I learned this week', ].map((example) => ( ))} )} {/* View mode toggle — shown on mobile only when a session is active */} {session && (
{session.ideas.filter(i => i.status !== 'dismissed').length} {t('brainstorm.ideasCount') || 'ideas'}
)}
{session && } {sessionLoading ? (
) : session && viewMode === 'list' ? ( /* Mobile list view */
{[1, 2, 3].map(wave => { const waveIdeas = session.ideas.filter(i => i.waveNumber === wave && i.status !== 'dismissed') if (waveIdeas.length === 0) return null return (
{t('brainstorm.waveBadge', { wave })} — {waveIdeas.length}
{waveIdeas.map(idea => ( ))}
) })}
) : session ? ( ) : (

{t('brainstorm.canvasWaitingHint')}

)} {session && (
{[1, 2, 3].map((w) => (
{t('brainstorm.waveBadge', { wave: w })}
))}
{canEdit && ( )} )} {session && ( setShowActivityFeed(!showActivityFeed)} t={t} /> )} {session && activeSessionId && ( setPlaybackIdeas(null)} /> )}
{selectedIdea && ( {/* Drag handle for mobile */}
{t('brainstorm.wave') || 'Wave'} {selectedIdea.waveNumber}
{selectedIdea.status === 'converted' && ( {t('brainstorm.noteCreated') || 'Note Created'} )} {canEdit && ( )}

{selectedIdea.title}

{t('brainstorm.novelty')}: {selectedIdea.noveltyScore ?? t('common.notAvailable')}/10
{selectedIdea.createdByType === 'human' ? ( <>
{(selectedIdea as any).creator?.name?.charAt(0)?.toUpperCase() || 'U'}
{t('brainstorm.humanIdea') || 'Human'} ) : ( <> {t('brainstorm.aiIdea') || 'AI'} )}

{selectedIdea.description}

{selectedIdea.connectionToSeed && (

{t('brainstorm.originConnection') || 'Origin connection'}

“{selectedIdea.connectionToSeed}”

)} {selectedIdea.noteRefs && selectedIdea.noteRefs.length > 0 && (

{t('brainstorm.ideaOrigin') || 'Origin of the idea'}

{selectedIdea.noteRefs.map((ref: BrainstormNoteRef) => { const isPositive = ref.relation === 'derived_from' || ref.relation === 'extends' const isNegative = ref.relation === 'opposes' const badgeColor = isPositive ? 'bg-emerald-500/10 dark:bg-emerald-500/20 text-emerald-600 dark:text-emerald-400 border-emerald-200 dark:border-emerald-700' : isNegative ? 'bg-rose-500/10 dark:bg-rose-500/20 text-rose-600 dark:text-rose-400 border-rose-200 dark:border-rose-700' : 'bg-amber-500/10 dark:bg-amber-500/20 text-amber-600 dark:text-amber-400 border-amber-200 dark:border-amber-700' const relLabel = t(`brainstorm.${ref.relation}`) || ref.relation const vis = (ref as any).visibility || 'participants' const isRestricted = vis === 'owner_only' return (
{relLabel} {isRestricted && !isGuest && ( {t('brainstorm.ownerBadge')} )}
{ref.note ? (

{ref.note.title || t('notes.untitled')}

) : (

{t('brainstorm.noNoteLink') || 'Purely generative idea'}

)}

{ref.explanation}

{ref.noteId && ( )}
) })}
)} {canEdit && (
)} {isGuest && (
{t('brainstorm.guestReadOnlyNotice')}
)}
)}
{sessions?.map((s) => (
{activeSessionId === s.id && ( )}
))}
{activeSessionId && session && ( )} {/* AI Summary modal */} {summaryOpen && ( setSummaryOpen(false)} > e.stopPropagation()} className="w-full max-w-lg bg-white dark:bg-[#1A1A1A] rounded-2xl shadow-2xl p-8 relative" >

{t('brainstorm.exportModalTitle') || 'Session summary'}

{summarize.isPending ? (
{[0, 1, 2].map(i => ( ))}
{t('brainstorm.generating') || 'Generating synthesis...'}
) : summaryText ? (

{summaryText}

) : (

{t('brainstorm.summaryError') || 'Could not generate summary'}

)}
{summaryText && ( )}
)}
{/* Rename session modal */} {renamingSession && ( setRenamingSession(null)} > e.stopPropagation()} className="w-full max-w-sm bg-white dark:bg-[#1A1A1A] rounded-2xl shadow-2xl p-6" >

{t('brainstorm.renameSession') || 'Rename session'}

setRenameInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') handleRenameSession(renamingSession!); if (e.key === 'Escape') setRenamingSession(null) }} className="w-full border border-border rounded-xl px-4 py-3 text-sm bg-transparent outline-none focus:border-brand-accent/40 focus:ring-2 focus:ring-brand-accent/10" />
)}
{impactToast && ( {impactToast.notesEnriched === 0 && impactToast.notesMarkedDry === 0 ? t('brainstorm.linkCopied') : <> {impactToast.notesEnriched > 0 && t('brainstorm.impactNotesEnriched', { count: impactToast.notesEnriched })} {impactToast.notesEnriched > 0 && impactToast.notesMarkedDry > 0 && t('brainstorm.impactSeparator')} {impactToast.notesMarkedDry > 0 && t('brainstorm.impactNotesMarkedDry', { count: impactToast.notesMarkedDry })} } )} {exportError && ( {exportError} )} {exportToast && (
{exportToast.noteTitle} {t('brainstorm.exportNotebookPrefix')} {exportToast.notebookName}
{t('brainstorm.exportOpening')}
)} {convertToast && (
{convertToast.noteTitle} {t('brainstorm.noteCreated') || 'Note Created'}
{t('brainstorm.exportOpening')}
)}
) }