import { useEffect, useState } from 'react'; import { supabase } from '../supabaseClient'; import { Sparkles, Check, AlertTriangle, Code, Award, ExternalLink, RefreshCw, ChevronDown } from 'lucide-react'; import CodeMirror from '@uiw/react-codemirror'; import { python } from '@codemirror/lang-python'; interface ReviewQueueProps { overrideProblemId: string | null; onClearOverride: () => void; } export default function ReviewQueue({ overrideProblemId, onClearOverride }: ReviewQueueProps) { const [reviews, setReviews] = useState([]); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [code, setCode] = useState(''); const [activeTab, setActiveTab] = useState<'ai' | 'offline'>('ai'); // AI evaluation states const [aiLoading, setAiLoading] = useState(false); const [aiResult, setAiResult] = useState(null); const [aiError, setAiError] = useState(null); // Reference code collapse state const [showRefCode, setShowRefCode] = useState(false); // Code explanation states const [explainLoading, setExplainLoading] = useState(false); const [explanationText, setExplanationText] = useState(null); const [explainError, setExplainError] = useState(null); // Autocomplete state (persistent) const [isAutocompleteEnabled, setIsAutocompleteEnabled] = useState(() => { const saved = localStorage.getItem('isAutocompleteEnabled'); return saved !== 'false'; }); const fetchQueue = async () => { setLoading(true); setAiResult(null); setAiError(null); setCode(''); setShowRefCode(false); setExplanationText(null); setExplainError(null); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const headers = { 'Authorization': `Bearer ${session.access_token}` }; let url = `${import.meta.env.VITE_API_BASE_URL}/api/reviews/due`; if (overrideProblemId) { url = `${import.meta.env.VITE_API_BASE_URL}/api/reviews/problems/id/${overrideProblemId}`; } const res = await fetch(url, { headers }); if (res.ok) { const data = await res.json(); setReviews(data); if (data && data.length > 0) { const activeReview = data[0]; const problem = activeReview.problems; if (problem && problem.starter_code) { setCode(problem.starter_code); } } } } catch (err) { console.error('Error fetching review queue:', err); } finally { setLoading(false); } }; useEffect(() => { fetchQueue(); }, [overrideProblemId]); const handleGrade = async (rating: 'Correct' | 'Mixed' | 'Incorrect') => { if (reviews.length === 0) return; const activeReview = reviews[0]; setSubmitting(true); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/reviews/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` }, body: JSON.stringify({ review_id: activeReview.id, box_level: activeReview.box_level, times_correct: activeReview.times_correct, total_attempts: activeReview.total_attempts, rating }) }); if (res.ok) { if (overrideProblemId) { // If we completed the custom override card, exit override mode onClearOverride(); } else { // Refresh the daily queue fetchQueue(); } } } catch (err) { console.error('Error logging review completion:', err); } finally { setSubmitting(false); } }; const handleAiEvaluation = async () => { if (reviews.length === 0 || !code.trim()) return; const activeReview = reviews[0]; const problem = activeReview.problems; setAiLoading(true); setAiResult(null); setAiError(null); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/reviews/evaluate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` }, body: JSON.stringify({ problem_name: problem.name, pattern: problem.pattern, optimal_time: problem.optimal_time, optimal_space: problem.optimal_space, code: code }) }); if (res.ok) { const result = await res.json(); setAiResult(result); } else { const errorData = await res.json(); setAiError(errorData.detail || 'Failed to analyze code.'); } } catch (err) { setAiError('Connection to evaluation server failed.'); } finally { setAiLoading(false); } }; const handleExplainCode = async () => { if (reviews.length === 0) return; const activeReview = reviews[0]; const problem = activeReview.problems; if (!problem || !problem.problem_number) return; setExplainLoading(true); setExplanationText(null); setExplainError(null); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/reviews/explain`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` }, body: JSON.stringify({ problem_number: problem.problem_number, method: problem.pattern }) }); if (res.ok) { const result = await res.json(); setExplanationText(result.explanation); } else { const errorData = await res.json(); setExplainError(errorData.detail || 'Failed to generate code explanation.'); } } catch (err) { setExplainError('Connection to explanation server failed.'); } finally { setExplainLoading(false); } }; if (loading) { return (
LOADING SYSTEM CARD...
); } if (reviews.length === 0) { return (

Queue Clear!

Fantastic work. All active Leitner cards are up to date. Come back tomorrow for new spaced-repetition reviews!

{overrideProblemId && ( )}
); } const activeReview = reviews[0]; const problem = activeReview.problems; return (
{/* Custom Override Header Banner */} {overrideProblemId && (
OVERRIDE: SELECTED MAP PROBLEM
)} {/* Problem Stats Header Card */}
BOX {activeReview.box_level} CORRECT: {activeReview.times_correct}/{activeReview.total_attempts} PATTERN: {problem.pattern}

{problem.name}

{problem.difficulty.toUpperCase()}
{problem.description && (
Problem Description
{problem.description.split('\n').map((line: string, i: number) => (

{line}

))}
)}
Optimal Time {problem.optimal_time}
Optimal Space {problem.optimal_space}
Doc Link Google Doc
Next Due {new Date(activeReview.next_review).toLocaleDateString()}
{/* Reference Code Expander (Chunky details element from code.html style) */}
setShowRefCode(!showRefCode)} className="font-arcade text-[10px] p-3 cursor-pointer flex justify-between items-center outline-none hover:bg-secondary-container transition-colors select-none font-bold" > {showRefCode ? 'HIDE REFERENCE CODE' : 'SHOW REFERENCE CODE'}
              {problem.reference_code || '# No reference code found for this problem'}
            
{problem.reference_code && (
{/* Explain Error */} {explainError && (
{explainError}
)} {/* Explanation display */} {explanationText && (

Code Breakdown

{parseMarkdown(explanationText)}
)}
)}
{/* Interactive Tabs */}
{/* Tab Content Box */}
{activeTab === 'ai' ? (
setCode(value)} placeholder="def solve(nums): # Write code here..." height="100%" theme="dark" extensions={[python()]} basicSetup={{ autocompletion: isAutocompleteEnabled, lineNumbers: true, foldGutter: true, dropCursor: true, allowMultipleSelections: true, indentOnInput: true, bracketMatching: true, closeBrackets: true, rectangularSelection: true, crosshairCursor: true, highlightActiveLine: true, highlightSelectionMatches: true, closeBracketsKeymap: true, defaultKeymap: true, searchKeymap: true, historyKeymap: true, foldKeymap: true, completionKeymap: true, lintKeymap: true, }} className="w-full h-full flex-1 min-h-[200px]" />
Gemini will inspect your algorithm for logical correctness, time/space limits, and edge cases.
{/* AI Review Loader Panel */} {aiLoading && (

AI COMPILING SUBMISSION

Verifying constraints, checking edge cases, evaluating big-O complexity...

)} {/* AI Error display */} {aiError && (
{aiError}
)} {/* AI Result details */} {aiResult && (

VERDICT RESULT

{aiResult.is_correct ? 'Logical verification passed successfully.' : 'Logical verification failed.'}

Detected Time

{aiResult.detected_complexity?.time || 'N/A'}

Detected Space

{aiResult.detected_complexity?.space || 'N/A'}

{aiResult.bugs && aiResult.bugs.length > 0 && (
Traceback / Edge Case Failures:
    {aiResult.bugs.map((bug: string, idx: number) => (
  • {bug}
  • ))}
)} {aiResult.key_suggestion && (
Refactoring Tip

{aiResult.key_suggestion}

)}

🤖 AI SUGGESTION: {aiResult.is_correct ? ( Click "Smashed It" to increment box level! ) : ( Click "Forgot" to reset back to Box 1. )}

)}
) : (

Offline grading instructions:

  1. Expand the "Show Reference Code" block above.
  2. Compare your logic or mental draft with the reference algorithm.
  3. Verify if you correctly recalled the patterns, Big-O limit, and transitions.
  4. Score yourself using the buttons below:
🔴 Forgot: resets box back to Box 1.
🟡 Mixed: keeps the current Box level.
🟢 Smashed It: increments box level by 1.
)}
{/* SRS Completion Logging Buttons */}
); } // Simple Markdown Parser Helpers function renderInlineMarkdown(text: string) { const regex = /(\*\*.*?\*\*|`.*?`)/g; const tokens = text.split(regex); return tokens.map((token, index) => { if (token.startsWith('**') && token.endsWith('**')) { return ( {token.slice(2, -2)} ); } if (token.startsWith('`') && token.endsWith('`')) { return ( {token.slice(1, -1)} ); } return token; }); } function parseMarkdown(text: string) { const parts = text.split(/(```[\s\S]*?```)/g); return parts.map((part, index) => { if (part.startsWith('```')) { const lines = part.split('\n'); const code = lines.slice(1, -1).join('\n'); return (
          {code}
        
); } const lines = part.split('\n'); return lines.map((line, lineIndex) => { const key = `${index}-${lineIndex}`; if (line.startsWith('### ')) { return

{line.substring(4)}

; } if (line.startsWith('## ')) { return

{line.substring(3)}

; } if (line.startsWith('# ')) { return

{line.substring(2)}

; } if (line.trim().startsWith('- ') || line.trim().startsWith('* ')) { const content = line.trim().substring(2); return (
{renderInlineMarkdown(content)}
); } if (line.trim() === '') { return
; } return

{renderInlineMarkdown(line)}

; }); }); }