"use client"; import { useCallback, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { ArrowLeft, ArrowRight, BookOpen, Check, ChevronDown, ClipboardList, FileQuestion, FileText, GraduationCap, Layers3, LoaderCircle, Moon, RefreshCw, Sparkles, Upload, Video, X, } from "lucide-react"; import { cn } from "@/lib/utils"; import { generateBrainNotes, generateBrainQuiz, generateBrainFlashcards, generateBrainVideoPlan, generateBrainPyqAnalysis, } from "@/lib/api/brain"; import { uploadDocument } from "@/lib/api/documents"; import type { ApiDocument, BrainGeneratePayload, BrainVideoPlanPayload, BrainVideoPlanResponse } from "@/lib/api/types"; import type { NotesOutput, QuizQuestionResponse, FlashcardResponseItem } from "@/lib/api/types"; import type { BrainPyqAnalysisOutput } from "@/lib/api/types"; import type { VideoMode } from "@/lib/api/types"; import { MATERIAL_UPLOAD_ACCEPT } from "@/lib/uploads/document-upload-contract"; import { validateStudyFile } from "./upload-guards"; type GeneratorStep = "upload" | "context" | "generating" | "results"; type StudyKit = { notes: NotesOutput | null; quiz: { questions: QuizQuestionResponse[] } | null; flashcards: { cards: FlashcardResponseItem[] } | null; videoPlan: BrainVideoPlanResponse | null; pyqAnalysis: BrainPyqAnalysisOutput | null; generatedAt: string; }; type ContextFields = { classLevel: string; board: string; subject: string; chapter: string; materialType: string; goal: string; availableTime: string; outputs: string[]; }; const OUTPUT_OPTIONS = [ { id: "notes", label: "Smart Notes", icon: ClipboardList }, { id: "quiz", label: "Practice Quiz", icon: FileQuestion }, { id: "flashcards", label: "Flashcards", icon: Layers3 }, { id: "video", label: "Study Video", icon: Video }, { id: "pyq", label: "PYQ Analysis", icon: GraduationCap }, ] as const; const CLASS_OPTIONS = ["Plus One", "Plus Two", "Class 10", "Class 9"]; const BOARD_OPTIONS = ["Kerala HSE", "CBSE", "ICSE", "Other"]; const SUBJECT_OPTIONS = ["Physics", "Chemistry", "Mathematics", "Biology", "Computer Science", "English"]; const GOAL_OPTIONS = [ { value: "exam", label: "Board exam prep" }, { value: "revision", label: "Quick revision" }, { value: "doubt", label: "Doubt solving" }, { value: "video-only", label: "Watch study video" }, { value: "deep-learn", label: "Deep learning" }, ]; const TIME_OPTIONS = [ { value: "30min", label: "30 minutes" }, { value: "1h", label: "1 hour" }, { value: "2h", label: "2 hours" }, { value: "5h", label: "5 hours" }, { value: "tonight", label: "All night" }, { value: "tomorrow", label: "Tomorrow" }, { value: "week", label: "A week+" }, ]; function buildPayload(topic: string, subject: string, sourceIds: string[], extra?: Record): BrainGeneratePayload { return { topic, source_id: sourceIds[0] ?? null, source_ids: sourceIds, subject, study_profile_id: null, level: null, language_preference: null, ...extra, } as BrainGeneratePayload; } function estimateStudyTime(context: ContextFields): string { const t = context.availableTime; if (t === "30min") return "30 minutes"; if (t === "1h") return "1 hour"; if (t === "2h") return "2 hours"; if (t === "5h") return "5 hours"; if (t === "tonight") return "3-4 hours"; if (t === "tomorrow") return "5-6 hours"; if (t === "week") return "8-10 hours across the week"; return "1-2 hours"; } function sourceTitle(doc: ApiDocument): string { return doc.title || doc.file_name || "Untitled source"; } export function StudyGenerator() { const router = useRouter(); const [step, setStep] = useState("upload"); const [uploadedDocs, setUploadedDocs] = useState([]); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const [dragOver, setDragOver] = useState(false); const fileInputRef = useRef(null); const [context, setContext] = useState({ classLevel: "", board: "", subject: "", chapter: "", materialType: "", goal: "", availableTime: "", outputs: [], }); const [questionStep, setQuestionStep] = useState(0); const [kit, setKit] = useState(null); const [genProgress, setGenProgress] = useState(""); const [genError, setGenError] = useState(null); const [expandedSection, setExpandedSection] = useState(null); const missingQuestions: Array<{ key: keyof ContextFields; label: string; options?: readonly string[] | readonly { value: string; label: string }[]; type?: "select" | "multiselect"; }> = []; if (!context.classLevel) missingQuestions.push({ key: "classLevel", label: "Which class?", options: CLASS_OPTIONS }); if (!context.board) missingQuestions.push({ key: "board", label: "Which syllabus/board?", options: BOARD_OPTIONS }); if (!context.subject) missingQuestions.push({ key: "subject", label: "Which subject?", options: SUBJECT_OPTIONS }); if (!context.chapter) missingQuestions.push({ key: "chapter", label: "Which chapter or topic?" }); if (!context.goal) missingQuestions.push({ key: "goal", label: "What's your goal?", options: GOAL_OPTIONS as unknown as readonly { value: string; label: string }[] }); if (!context.availableTime) missingQuestions.push({ key: "availableTime", label: "How much time do you have?", options: TIME_OPTIONS as unknown as readonly { value: string; label: string }[] }); if (context.outputs.length === 0) missingQuestions.push({ key: "outputs", label: "What output do you want?", options: OUTPUT_OPTIONS.map(o => ({ value: o.id, label: o.label })), type: "multiselect" }); const currentQuestion = questionStep < missingQuestions.length ? missingQuestions[questionStep] : null; const allContextCollected = missingQuestions.length === 0; const handleFile = useCallback(async (file: File) => { const validation = validateStudyFile(file, "material"); if (!validation.ok) { setUploadError(validation.message); return; } setIsUploading(true); setUploadError(null); try { const doc = await uploadDocument({ file, title: file.name.replace(/\.[^/.]+$/, "") }); setUploadedDocs(prev => [...prev, doc]); } catch { setUploadError("Upload failed. Try again."); } finally { setIsUploading(false); } }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setDragOver(false); const file = e.dataTransfer.files?.[0]; if (file) handleFile(file); }, [handleFile]); const handleContextSelect = (key: keyof ContextFields, value: string | string[]) => { setContext(prev => ({ ...prev, [key]: value })); setQuestionStep(s => s + 1); }; const handleOutputToggle = useCallback((id: string) => { setContext(prev => ({ ...prev, outputs: prev.outputs.includes(id) ? prev.outputs.filter(o => o !== id) : [...prev.outputs, id], })); }, []); const handleGenerate = useCallback(async () => { setGenError(null); setStep("generating"); const topic = context.chapter || uploadedDocs[0]?.chapter || "this topic"; const subject = context.subject || uploadedDocs[0]?.subject || "General"; const sourceIds = uploadedDocs.map(d => d.id); const results: StudyKit = { notes: null, quiz: null, flashcards: null, videoPlan: null, pyqAnalysis: null, generatedAt: new Date().toISOString() }; try { if (context.outputs.includes("notes") || context.outputs.length === 0) { setGenProgress("Creating study summary and smart notes..."); try { const notes = await generateBrainNotes(buildPayload(topic, subject, sourceIds, { options: { mode: "smart_notes" } })); results.notes = notes.output; } catch { /* notes unavailable */ } } if (context.outputs.includes("quiz") || context.outputs.length === 0) { setGenProgress("Building practice quiz..."); try { const quiz = await generateBrainQuiz(buildPayload(topic, subject, sourceIds, { options: { num_questions: 8 } })); results.quiz = quiz.output; } catch { /* quiz unavailable */ } } if (context.outputs.includes("flashcards") || context.outputs.length === 0) { setGenProgress("Making flashcards..."); try { const cards = await generateBrainFlashcards(buildPayload(topic, subject, sourceIds, { options: { card_count: 10 } })); results.flashcards = cards.output; } catch { /* flashcards unavailable */ } } if (context.outputs.includes("video") || context.outputs.length === 0) { setGenProgress("Planning study video..."); try { const videoPayload: BrainVideoPlanPayload = { topic, video_mode: "exam_focus" as VideoMode, teaching_style: "normal_teacher" as const, format: "16:9" as const, source_id: sourceIds[0] ?? null, source_ids: sourceIds, level: null, }; const videoPlan = await generateBrainVideoPlan(videoPayload); results.videoPlan = videoPlan; } catch { /* video plan unavailable */ } } if (context.outputs.includes("pyq")) { setGenProgress("Analyzing question patterns..."); try { const pyq = await generateBrainPyqAnalysis(buildPayload(topic, subject, sourceIds, { options: { raw_text: `Analyze PYQ patterns for ${topic} - ${subject} ${context.board}` } })); results.pyqAnalysis = pyq.output; } catch { /* pyq unavailable */ } } setKit(results); setStep("results"); } catch (err) { setGenError(err instanceof Error ? err.message : "Generation failed. Try again."); setStep("upload"); } finally { setGenProgress(""); } }, [context, uploadedDocs]); // triggered directly when context collection completes const renderUpload = () => (

Study Generator

Upload notes, textbook pages, question papers, or any study material. DocDoe generates your complete study kit.

{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={handleDrop} onClick={() => fileInputRef.current?.click()} className={cn( "flex cursor-pointer flex-col items-center gap-4 rounded-2xl border-2 border-dashed p-12 transition", dragOver ? "border-white/40 bg-white/[0.06]" : "border-white/[0.12] bg-white/[0.02] hover:border-white/25 hover:bg-white/[0.04]", )} >

Drop your file here or click to browse

Selectable-text PDF or TXT · Max 20 MB

{ const f = e.target.files?.[0]; if (f) handleFile(f); e.target.value = ""; }} />
{isUploading &&
Uploading...
} {uploadError &&
{uploadError}
} {uploadedDocs.length > 0 && (

Uploaded ({uploadedDocs.length})

{uploadedDocs.map(doc => (

{sourceTitle(doc)}

{doc.file_type || "document"}

))}
)} {uploadedDocs.length > 0 && (
)}
or just type a topic
); const renderContext = () => { if (!currentQuestion && allContextCollected) return null; return (
{missingQuestions.slice(0, 7).map((q, i) => (
))}

{currentQuestion?.label || "One more thing..."}

{currentQuestion?.key === "chapter" && (
{ if (e.key === "Enter" && (e.target as HTMLInputElement).value.trim()) handleContextSelect("chapter", (e.target as HTMLInputElement).value.trim()); }} />
)} {currentQuestion?.options && currentQuestion.type !== "multiselect" && (
{(currentQuestion.options as readonly string[] | readonly { value: string; label: string }[]).map(opt => { const value = typeof opt === "string" ? opt : opt.value; const label = typeof opt === "string" ? opt : opt.label; const selected = context[currentQuestion!.key] === value; return (); })}
)} {currentQuestion?.type === "multiselect" && currentQuestion.options && (
{(currentQuestion.options as readonly { value: string; label: string }[]).map(opt => { const Icon = OUTPUT_OPTIONS.find(o => o.id === opt.value)?.icon || FileText; const selected = context.outputs.includes(opt.value); return (); })}
)}

Your context

{context.classLevel && {context.classLevel}} {context.board && {context.board}} {context.subject && {context.subject}} {context.chapter && {context.chapter}} {context.goal && {GOAL_OPTIONS.find(g => g.value === context.goal)?.label || context.goal}} {context.availableTime && {TIME_OPTIONS.find(t => t.value === context.availableTime)?.label || context.availableTime}} {context.outputs.map(o => {OUTPUT_OPTIONS.find(op => op.id === o)?.label || o})}
); }; const renderGenerating = () => (

Generating your study kit

{genProgress}

{(context.outputs.length > 0 ? context.outputs : OUTPUT_OPTIONS.map(o => o.id)).map(o => { const opt = OUTPUT_OPTIONS.find(op => op.id === o); return {opt?.label || o}; })}
{genError &&
{genError}
}
); const renderResults = () => { if (!kit) return null; const sections: Array<{ id: string; label: string; icon: typeof Sparkles; content: React.ReactNode; }> = []; if (kit.notes) { sections.push({ id: "notes", label: "Smart Notes", icon: ClipboardList, content: (
{kit.notes.key_points?.length > 0 && (

Key Points

    {kit.notes.key_points.slice(0, 8).map((kp: string, i: number) => (
  • {kp}
  • ))}
)} {kit.notes.summary && (

Summary

{kit.notes.summary}

)} {kit.notes.exam_keywords && kit.notes.exam_keywords.length > 0 && (

Exam Keywords

{kit.notes.exam_keywords.slice(0, 10).map((kw: string, i: number) => ( {kw} ))}
)} {!kit.notes.key_points && !kit.notes.summary &&

Notes generated.

}
), }); } if (kit.quiz) { sections.push({ id: "quiz", label: "Practice Quiz", icon: FileQuestion, content: (
{(kit.quiz.questions || []).slice(0, 5).map((q, i) => (
{i + 1}

{q.question}

{q.options?.slice(0, 4).map((opt, oi) => (
{opt}
))}
))} {(!kit.quiz.questions || kit.quiz.questions.length === 0) &&

Quiz questions will be ready in the full session.

}
), }); } if (kit.flashcards) { sections.push({ id: "flashcards", label: "Flashcards", icon: Layers3, content: (
{(kit.flashcards.cards || []).slice(0, 6).map((card: { front: string; back: string }, i: number) => (

FRONT

{card.front}

BACK

{card.back}

))} {(!kit.flashcards.cards || kit.flashcards.cards.length === 0) &&

Flashcards will be created in the full session.

}
), }); } if (kit.videoPlan) { sections.push({ id: "video", label: "Study Video Plan", icon: Video, content: (

Title

{kit.videoPlan.title || "Study video"}

Scenes

{kit.videoPlan.scenes?.length || 0} beats

Duration

{kit.videoPlan.target_duration_range || "~10 min"}

{(kit.videoPlan.scenes || []).slice(0, 5).map((scene: { title?: string; duration?: string; narration?: string }, i: number) => (
{i + 1}

{scene.title || `Scene ${i + 1}`}

{scene.duration &&

{scene.duration}

}
{scene.narration &&

{scene.narration}

}
))}
), }); } if (kit.pyqAnalysis) { sections.push({ id: "pyq", label: "PYQ Pattern Insights", icon: GraduationCap, content: (

{kit.pyqAnalysis.summary}

{(kit.pyqAnalysis.repeated_topics || []).slice(0, 5).map((t: { topic: string; chapter: string; count: number }, i: number) => (

REPEATED TOPIC

{t.topic}

{t.count > 0 &&

Appeared {t.count}×

}
))}
{!kit.pyqAnalysis.repeated_topics &&

PYQ analysis uses uploaded papers. Add question papers for deeper pattern detection.

}
), }); } return (

Your Study Kit

{[context.subject, context.chapter, context.board, context.classLevel].filter(Boolean).join(" · ")}

Estimated study time: {estimateStudyTime(context)} · {uploadedDocs.length} source{uploadedDocs.length !== 1 ? "s" : ""}

{uploadedDocs.length > 0 && (

Sources used

{uploadedDocs.map(doc => ( {sourceTitle(doc)} ))}
)} {sections.map(section => (
{expandedSection === section.id &&
{section.content}
}
))}

Revision Checklist

{["Review summary and key concepts", "Practice with generated quiz questions", "Review flashcards for active recall", "Watch or plan study video", "Review PYQ patterns for exam strategy", "Self-test with closed-book recall"].map((item, i) => ( ))}

Generated {new Date(kit.generatedAt).toLocaleTimeString()} · Sources: {uploadedDocs.length} · ~{estimateStudyTime(context)}

); }; return (
{step === "results" && (

Study Generator

Study kit ready

)} {step === "upload" && renderUpload()} {step === "context" && renderContext()} {step === "generating" && renderGenerating()} {step === "results" && renderResults()}
); }