import React, { useMemo, useState, useCallback, useEffect, useRef, type Key } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { ArrowLeft, ChevronRight, Database, Monitor, Network, FileText, Layers, BookOpen, ChevronDown, ChevronUp, Lightbulb, CheckCircle2, Circle, Trophy, Zap } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { csFundamentalsTracks, type CSFundamentalsTrack } from '@/data/csFundamentalsData'; import { cn } from '@/lib/utils'; const STORAGE_KEY = 'cs_fundamentals_completed'; function loadCompleted(): Record { try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch { return {}; } } function countTopics(track: CSFundamentalsTrack) { return track.modules.reduce((t, m) => t + m.topics.length, 0); } const getTrackIcon = (id: string, size = 40) => { switch (id) { case 'database': return ; case 'os': return ; case 'computer-network': return ; default: return ; } }; // ── Topic Row ───────────────────────────────────────────────────────────────── function TopicRow({ trackId, moduleId, topic, index, animDelay, completed, onToggle }: { key?: Key; trackId: string; moduleId: string; topic: { title: string; explanation: string }; index: number; animDelay: number; completed: boolean; onToggle: (key: string, title: string) => void; }) { const [open, setOpen] = useState(false); const key = `${trackId}__${moduleId}__${index}`; return (
{/* Main row */}
{/* Complete toggle */} {/* Number */}
{index + 1}
{/* Title */} {/* Expand */}
{/* Expandable explanation */} {open && (

{topic.explanation}

)}
); } interface CSFundamentalsSectionProps { onSolve?: (id: string, title: string) => void; onNavigateToDBMS?: () => void; onNavigateToCN?: () => void; onNavigateToOS?: () => void; } // ── Main ────────────────────────────────────────────────────────────────────── export default function CSFundamentalsSection({ onSolve, onNavigateToDBMS, onNavigateToCN, onNavigateToOS }: CSFundamentalsSectionProps = {}) { const [selectedTrack, setSelectedTrack] = useState(null); const [completed, setCompleted] = useState>(loadCompleted); // Dynamic counts for actual pages const [dbmsCount, setDbmsCount] = useState(null); const [osCount, setOsCount] = useState(null); const [cnCount, setCnCount] = useState(null); useEffect(() => { import('@/lib/dbmsClient').then(m => m.fetchDBMSTopics()).then(res => setDbmsCount(res.length)).catch(() => {}); import('@/lib/osClient').then(m => m.fetchOSSections()).then(res => setOsCount(res.length)).catch(() => {}); import('@/lib/cnClient').then(m => m.fetchCNTopics()).then(res => setCnCount(res.length)).catch(() => {}); }, []); const topicCounts = useMemo( () => Object.fromEntries(csFundamentalsTracks.map(t => [t.id, countTopics(t)])), [] ); const toggleTopic = useCallback((key: string, _title: string) => { // onSolve MUST be outside setCompleted — React 18 Strict Mode runs updaters // twice in dev, so any API call inside fires twice (duplicate transactions). setCompleted(prev => { const isCompleting = !prev[key]; const next = { ...prev, [key]: isCompleting }; localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); return next; }); }, []); // Fire onSolve exactly once when a topic transitions false → true. const prevCompletedRef = useRef>(completed); useEffect(() => { const prev = prevCompletedRef.current; for (const key of Object.keys(completed)) { if (completed[key] && !prev[key] && onSolve) { // Find the title for this key by scanning all tracks let foundTitle = key; for (const track of csFundamentalsTracks) { for (const mod of track.modules) { mod.topics.forEach((topic, ti) => { if (`${track.id}__${mod.id}__${ti}` === key) { foundTitle = topic.title; } }); } } onSolve(`cs-${key}`, foundTitle); } } prevCompletedRef.current = completed; }, [completed, onSolve]); // Track-level progress const trackProgress = useMemo(() => { if (!selectedTrack) return { done: 0, total: 0, pct: 0 }; let done = 0, total = 0; selectedTrack.modules.forEach((m, mi) => { m.topics.forEach((_, ti) => { total++; if (completed[`${selectedTrack.id}__${m.id}__${ti}`]) done++; }); }); return { done, total, pct: total > 0 ? Math.round((done / total) * 100) : 0 }; }, [selectedTrack, completed]); // Grid card track completion const getGridProgress = (track: CSFundamentalsTrack) => { if (track.id === 'database') { const dbmsProgress = JSON.parse(localStorage.getItem('ryp_dbms_progress') || '{}'); const done = Object.values(dbmsProgress).filter(Boolean).length; const total = dbmsCount || 7; return { done, total, pct: total > 0 ? Math.round((done / total) * 100) : 0, label: 'chapters' }; } if (track.id === 'os') { const osProgress = JSON.parse(localStorage.getItem('ryp_os_progress') || '{}'); const done = Object.values(osProgress).filter(Boolean).length; const total = osCount || 6; return { done, total, pct: total > 0 ? Math.round((done / total) * 100) : 0, label: 'sections' }; } if (track.id === 'computer-network') { const cnProgress = JSON.parse(localStorage.getItem('ryp_cn_progress') || '{}'); const done = Object.values(cnProgress).filter(Boolean).length; const total = cnCount || 12; return { done, total, pct: total > 0 ? Math.round((done / total) * 100) : 0, label: 'chapters' }; } let done = 0, total = 0; track.modules.forEach(m => m.topics.forEach((_, ti) => { total++; if (completed[`${track.id}__${m.id}__${ti}`]) done++; })); return { done, total, pct: total > 0 ? Math.round((done / total) * 100) : 0, label: 'topics' }; }; const getDynamicLabel = (trackId: string) => { if (trackId === 'database') return dbmsCount !== null ? `${dbmsCount} Chapters` : 'Loading...'; if (trackId === 'os') return osCount !== null ? `${osCount} Sections` : 'Loading...'; if (trackId === 'computer-network') return cnCount !== null ? `${cnCount} Chapters` : 'Loading...'; return ''; }; return (
{/* Header */}
{!selectedTrack ? (

CS Fundamentals

) : ( <>
CS Fundamentals / {selectedTrack.title}

{selectedTrack.title}

Master the core concepts of {selectedTrack.title.toLowerCase()} essential for top-tier engineering interviews and system design.

)}
{selectedTrack && ( )}
{/* ── Track Selection Grid ── */} {!selectedTrack && ( {csFundamentalsTracks.map((track, idx) => { const { done, total, pct, label } = getGridProgress(track); const dynamicLabel = getDynamicLabel(track.id); return ( { if (track.id === 'database' && onNavigateToDBMS) { onNavigateToDBMS(); } else if (track.id === 'computer-network' && onNavigateToCN) { onNavigateToCN(); } else if (track.id === 'os' && onNavigateToOS) { onNavigateToOS(); } else { setSelectedTrack(track); } }} className="group relative flex min-h-[290px] w-full flex-col overflow-hidden rounded-[32px] border border-zinc-800/80 bg-zinc-950/40 p-8 text-left backdrop-blur-sm transition-all duration-500 hover:-translate-y-1 hover:border-cyan-500/30 hover:bg-zinc-900/60 hover:shadow-2xl cursor-pointer" >
{getTrackIcon(track.id, 28)}
{dynamicLabel ? dynamicLabel : `${track.modules.length} Modules`}

{track.title}

{dynamicLabel ? track.level : `${total} topics / ${track.level}`}

{/* Progress bar */}
{done}/{total} {label} done {pct}%
{pct === 100 && (
Track Completed!
)} ); })} )} {/* ── Track Detail ── */} {selectedTrack && ( {/* ── Coding-Ninjas-style Track Progress Banner ── */}
{/* Left: info */}
{getTrackIcon(selectedTrack.id, 28)}
Track Progress
{trackProgress.done} / {trackProgress.total} topics
{selectedTrack.modules.length} modules | {selectedTrack.level} | {selectedTrack.duration}
{/* Right: circular-ish progress display */}
Completion {trackProgress.pct}%
{/* Milestone badges */}
{[25, 50, 75, 100].map(milestone => (
= milestone ? 'border-cyan-500/40 bg-cyan-500/20 text-cyan-300 shadow-[0_0_8px_rgba(6,182,212,0.3)]' : 'border-zinc-800 bg-zinc-900/50 text-zinc-700' )} > = milestone ? 'text-cyan-400' : 'text-zinc-700'} /> {milestone}%
))}
{trackProgress.pct === 100 && (
Track Mastered!
)}
{/* ── Module Tables ── */}
{selectedTrack.modules.map((module, mi) => { const modDone = module.topics.filter((_, ti) => completed[`${selectedTrack.id}__${module.id}__${ti}`]).length; const modTotal = module.topics.length; const modPct = modTotal > 0 ? Math.round((modDone / modTotal) * 100) : 0; return (
{/* Module header */}
0 ? 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30' : 'bg-cyan-500/10 text-cyan-400 ring-cyan-500/20' )}> {modDone === modTotal && modTotal > 0 ? : }

{mi + 1}. {module.title}

{module.summary}

{/* Mini progress */}
0 ? 'bg-emerald-500' : 'bg-cyan-500' )} style={{ width: `${modPct}%` }} />
0 ? 'text-emerald-400' : 'text-cyan-400' )}>{modDone}/{modTotal}
{modTotal} Topics
{/* Table column header */}
Done # Topic
{/* Topic rows */}
{module.topics.map((topic, ti) => ( ))}
); })}
)}
); }