import { useEffect, useState, useMemo } from 'react'; import { motion, useScroll, useSpring } from 'motion/react'; import { ChevronLeft, Loader2, Book, Clock, HelpCircle, Bot, Sparkles, Table2 } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeHighlight from 'rehype-highlight'; import { fetchDBMSTopicContent, type DBMSTopicContent } from '@/lib/dbmsClient'; import { fixMarkdownBold, cleanMarkdown, processAsciiTableToMarkdown } from '@/lib/markdownUtils'; import 'highlight.js/styles/github-dark.css'; import { cn } from '@/lib/utils'; import DBMSArchitectureDiagram from './DBMSArchitectureDiagram'; import DBMSAdvantagesDiagram from './DBMSAdvantagesDiagram'; import DBMSTierArchitectureDiagram from './DBMSTierArchitectureDiagram'; import DBMSTypesDiagram from './DBMSTypesDiagram'; import ERModelDiagram from './ERModelDiagram'; import OODMDiagram from './OODMDiagram'; import RelationalModelDiagram from './RelationalModelDiagram'; import DataAbstractionLevelsDiagram from './DataAbstractionLevelsDiagram'; import SQLTableOutput from './SQLTableOutput'; import RelationalTableConceptsDiagram from './RelationalTableConceptsDiagram'; import ConstraintsDiagram from './ConstraintsDiagram'; import DenormalizationDiagram from './DenormalizationDiagram'; import KeysInDBMSDiagram from './KeysInDBMSDiagram'; import NormalizationDiagram from './NormalizationDiagram'; import RelationshipsInDBMSDiagram from './RelationshipsInDBMSDiagram'; import ACIDTransactionsDiagram from './ACIDTransactionsDiagram'; import IndexingDiagram from './IndexingDiagram'; import JoinsDiagram from './JoinsDiagram'; import ViewsTriggersDiagram from './ViewsTriggersDiagram'; import ConcurrencyControlDiagram from './ConcurrencyControlDiagram'; interface DBMSContentPageProps { topicNo: number; onBack: () => void; } interface QABlock { id: string; question: string; answerMarkdown: string; } /** * fixMarkdownBold and cleanMarkdown are imported from @/lib/markdownUtils. * See that file for full documentation. */ function parseDBMSContent(raw: string): QABlock[] { let cleaned = raw.replace(/ /g, '').replace(/\\&/g, '&'); cleaned = fixMarkdownBold(cleaned); cleaned = processAsciiTableToMarkdown(cleaned); // Split by **Q or **Q: or **Q. or **Q:- const chunks = cleaned.split(/\*\*Q[:\.\-]?\s*/i); const blocks: QABlock[] = []; chunks.forEach((chunk, index) => { if (!chunk.trim()) return; const parts = chunk.split(/\*\*/); let question = parts[0].trim(); let answer = parts.slice(1).join('**').trim(); // Remove "**Ans:-**" and any variations — handles **Ans:**, **Ans:-**, **Ans**, Ans:, etc. answer = answer.replace(/^\s*\**\s*Ans(?:wer)?\s*[:\-\.]?\s*\**\s*/i, '').trim(); // Fix stray markdown artifacts like `_**` at the beginning of the answer answer = answer.replace(/^[_*]+\s*?\n+/g, '').trim(); answer = answer.replace(/^_\*\*\s*/g, '').trim(); // Strip optional leading ** before numbered items and remove trailing --** or ** answer = answer.replace(/^(?:\*\*|)(\d+[.\)]?\s*.+?)(?:--\*\*|\*\*)\s*$/gm, '$1').trim(); // Convert "1. Title" / "1\. Title" / "1.Title" at start of lines into ### headings answer = answer.replace(/^(\d+)[\\\.]\s*(.+)$/gm, '### $1. $2'); // ── FINAL PASS: clean all remaining stray artifacts ── answer = cleanMarkdown(answer); blocks.push({ id: `qa-${index}`, question, answerMarkdown: answer }); }); return blocks; } export default function DBMSContentPage({ topicNo, onBack }: DBMSContentPageProps) { const [content, setContent] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { scrollYProgress } = useScroll(); const scaleX = useSpring(scrollYProgress, { stiffness: 100, damping: 30, restDelta: 0.001 }); useEffect(() => { const loadContent = async () => { setLoading(true); setError(null); try { const data = await fetchDBMSTopicContent(topicNo); setContent(data); } catch (err) { console.error('[DBMSContentPage] fetch error:', err); setError('Failed to load topic content. Please try again.'); } finally { setLoading(false); } }; void loadContent(); window.scrollTo(0, 0); }, [topicNo]); const qaBlocks = useMemo(() => { if (!content?.content) return []; return parseDBMSContent(content.content); }, [content]); if (loading) { return (
Fetching topic content...
); } if (error || !content) { return (

{error || 'Topic not found.'}

); } return (
{/* Top Reading Progress Bar */} {/* Floating Back Button (Desktop) */}
{/* Sticky Header */}
Topic #{content.topic_no} DBMS Fundamentals

{content.topic_name}

{/* Title Section */}

{content.topic_name}

~{Math.max(1, Math.ceil(content.content.length / 1500))} min read {qaBlocks.length} Sections
{/* Q&A Blocks */}
{qaBlocks.map((block, idx) => { return ( {/* Question Header */} {block.question && (

{block.question}

)} {/* Answer Body */}
{ const text = String(children); // Check if this is a Rule paragraph if (/^\*\*Rule \d+:/.test(text)) { return (

{text.match(/\d+/)?.[0]} {text.replace(/^\*\*Rule \d+:\*\*\s*/, '')}

); } return

{children}

; }, strong: ({ node, ...props }) => ( ), a: ({ node, ...props }) => ( ), em: ({ node, ...props }) => ( ), h3: ({ node, ...props }) => (

), h4: ({ node, ...props }) => (

), code: ({ node, className, children, ...props }) => { const match = /language-(\w+)/.exec(className || ''); const isInline = !match && !String(children).includes('\n'); if (isInline) { return {children}; } return (
{match?.[1] || 'Code'}
{children}
); }, table: ({ node, ...props }) => ( {/* Glowing top accent */}
{/* Table title bar */}
Data Table
), thead: ({ node, ...props }) => ( ), th: ({ node, ...props }) => ( , tr: ({ node, ...props }) => ( ), td: ({ node, children, ...props }) => { const text = String(children ?? '').trim(); const isNA = text === '' || text === 'N/A'; return ( ); }, blockquote: ({ node, ...props }) => (
), ul: ({ node, ...props }) =>
    , li: ({ node, ...props }) => (
  • ) }} > {block.answerMarkdown} {block.question && /what is dbms/i.test(block.question) && ( )} {block.question && /advantages/i.test(block.question) && ( )} {block.question && /architecture|tier/i.test(block.question) && ( )} {block.question && /types/i.test(block.question) && ( )} {block.question && /er model|entity.?relationship/i.test(block.question) && ( )} {block.question && /object.?oriented|oodm/i.test(block.question) && ( )} {block.question && /relational model/i.test(block.question) && ( )} {block.question && /abstraction|logical|physical|conceptual/i.test(block.question) && ( )} {block.question && /sql|query|output|foreign key|select/i.test(block.question) && ( )} {block.question && /table|row|column|tuple|attribute/i.test(block.question) && ( )} {block.question && /constraint|not null|unique|default|check/i.test(block.question) && ( )} {block.question && /denormalization|denormalize|redundant/i.test(block.question) && ( )} {block.question && /primary key|foreign key|candidate key|super key|keys in dbms/i.test(block.question) && ( )} {block.question && /normalization|1nf|2nf|3nf|bcnf|normal form/i.test(block.question) && ( )} {block.question && /relationship|one to one|one to many|many to many|1-1|1-m|m-m/i.test(block.question) && ( )} {block.question && /transaction|acid|commit|rollback|atomicity|consistency|isolation|durability/i.test(block.question) && ( )} {block.question && /index|b\+tree|btree|b tree|hash index|clustered|non.clustered|secondary index/i.test(block.question) && ( )} {block.question && /join|inner join|outer join|left join|right join|cross join|self join/i.test(block.question) && ( )} {block.question && /view|trigger|stored procedure|virtual table|materialized/i.test(block.question) && ( )} {block.question && /concurrency|lock|two.phase|schedule|serializable|mvcc|deadlock control/i.test(block.question) && ( )} ); })} {/* Footer info */}

    Topic Completed

    You've reached the end of this topic. Head back to the main menu to track your progress and continue learning.

    ); }
), tbody: ({ node, ...props }) =>
{isNA ? N/A : children}