"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { motion } from "framer-motion"; import { AlgorithmKey, LanguageCode, Step } from "@/lib/types"; import { runAlgorithm } from "@/lib/algorithms"; import { algorithmDescriptions } from "@/lib/descriptions"; import { complexityClass } from "@/lib/complexity"; import { t, narrate } from "@/lib/i18n"; import Bars from "@/components/Bars"; import GrowthChart from "@/components/GrowthChart"; import Narration from "@/components/Narration"; import StatsPanel from "@/components/StatsPanel"; import PlaybackControls, { Speed } from "@/components/PlaybackControls"; import { AlgorithmPicker } from "@/components/Pickers"; import SiteFooter from "@/components/SiteFooter"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; const SPEED_MS: Record = { slow: 260, normal: 120, fast: 55, turbo: 18 }; function randomArray(size: number): number[] { return Array.from({ length: size }, () => Math.floor(Math.random() * 100) + 1); } export default function Page() { const [algorithm, setAlgorithm] = useState("bubble"); const [language, setLanguage] = useState("en"); const [arraySize, setArraySize] = useState(24); const [baseArray, setBaseArray] = useState(() => randomArray(24)); const [steps, setSteps] = useState([]); const [stepIndex, setStepIndex] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [speed, setSpeed] = useState("normal"); const timeoutRef = useRef | null>(null); // (Re)compute the full step-by-step story whenever the algorithm or the // underlying data changes. Everything downstream just plays it back. useEffect(() => { setSteps(runAlgorithm(algorithm, baseArray)); setStepIndex(0); setIsPlaying(false); }, [algorithm, baseArray]); useEffect(() => { return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, []); useEffect(() => { if (!isPlaying) return; if (stepIndex >= steps.length - 1) { setIsPlaying(false); return; } timeoutRef.current = setTimeout(() => { setStepIndex((i) => Math.min(i + 1, steps.length - 1)); }, SPEED_MS[speed]); return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, [isPlaying, stepIndex, steps.length, speed]); const handleTogglePlay = useCallback(() => { if (stepIndex >= steps.length - 1) { setStepIndex(0); } setIsPlaying((p) => !p); }, [stepIndex, steps.length]); const handleShuffle = useCallback(() => { setBaseArray(randomArray(arraySize)); }, [arraySize]); const handleReset = useCallback(() => { setBaseArray(randomArray(arraySize)); }, [arraySize]); const handleArraySizeChange = useCallback((n: number) => { setArraySize(n); setBaseArray(randomArray(n)); }, []); const handleScrub = useCallback((i: number) => { setIsPlaying(false); setStepIndex(Math.min(i, steps.length - 1)); }, [steps.length]); const currentStep: Step | undefined = steps[stepIndex]; const description = algorithmDescriptions[algorithm]?.[language] ?? algorithmDescriptions[algorithm]?.en; const cclass = complexityClass[algorithm]; const pivotIndex = useMemo(() => { if (!currentStep) return undefined; if (currentStep.action === "select-pivot" || currentStep.action === "pivot-placed") return currentStep.indices[0]; return undefined; }, [currentStep]); const narration = useMemo(() => { if (!currentStep) return ""; return narrate(currentStep, algorithm, description?.title ?? algorithm, language); }, [currentStep, algorithm, description, language]); const growthLabels = { "n+k": "O(n + k)", nlogn: "O(n log n)", "n2-adaptive": "O(n log\u00B2 n)", n2: "O(n\u00B2)", } as const; return (

{t(language, "brand")}

{t(language, "tagline")}

{description && ( {description.title}
{description.timeComplexity}
{description.spaceComplexity}

{description.description}

)}

{t(language, "growthTitle")}

{t(language, "growthCaption")}

); }