"use client"; import React, { useState, useCallback, useRef } from "react"; import { motion, AnimatePresence, Reorder } from "framer-motion"; import GameButton from "@/components/ui/GameButton"; /* ═══════════════════ Types ═══════════════════ */ export interface LogicChainProps { stageIndex: number; totalStages: number; topic: string; description: string; nodes: string[]; // correct order feedbackMsg: { success: string; error: string; hint: string }; onComplete: () => void; onError?: () => void; onHintUse: () => boolean; } /* ═══════════════════ Helpers ═══════════════════ */ function shuffle(arr: T[]): T[] { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } /* Owl mascot (inline SVG) */ function OwlMascotSmall() { return ( ); } /* ═══════════════════ Accent colours per step ═══════════════════ */ const STEP_COLORS = [ "from-sky-400 to-sky-500", "from-brand-teal to-[#5fb3af]", "from-amber-400 to-amber-500", "from-brand-coral to-red-400", "from-violet-400 to-purple-500", "from-brand-green to-emerald-500", ]; const STEP_BORDER = [ "border-sky-300", "border-brand-teal", "border-amber-300", "border-brand-coral", "border-violet-300", "border-brand-green", ]; /* ═══════════════════ Component ═══════════════════ */ export default function LogicChain({ stageIndex, totalStages, topic, description, nodes, feedbackMsg, onComplete, onError, onHintUse, }: LogicChainProps) { // Shuffle on first render (useRef so it only shuffles once) const initialOrder = useRef(shuffle(nodes)); const [order, setOrder] = useState(initialOrder.current); const [result, setResult] = useState<"correct" | "wrong" | null>(null); const [wrongIndices, setWrongIndices] = useState([]); const [hintUsed, setHintUsed] = useState(false); const [completed, setCompleted] = useState(false); const [lockedCount, setLockedCount] = useState(0); // how many from the top are locked (via hints) /* CHECK */ const handleCheck = useCallback(() => { if (completed) return; // Compare with correct order const isCorrect = order.every((n, i) => n === nodes[i]); if (isCorrect) { setResult("correct"); setCompleted(true); setWrongIndices([]); setTimeout(() => onComplete(), 600); } else { // Find wrong positions const wrong: number[] = []; order.forEach((n, i) => { if (n !== nodes[i]) wrong.push(i); }); setWrongIndices(wrong); setResult("wrong"); onError?.(); setTimeout(() => { setResult(null); setWrongIndices([]); }, 1200); } }, [order, nodes, completed, onComplete]); /* Hint: lock the next correct item in place */ const handleHint = useCallback(() => { if (hintUsed || completed) return; const canAfford = onHintUse(); if (!canAfford) return; setHintUsed(true); // Place the next correct node at the right position const nextCorrect = nodes[lockedCount]; const newOrder = [...order]; const currentIdx = newOrder.indexOf(nextCorrect); if (currentIdx !== lockedCount) { // Swap [newOrder[lockedCount], newOrder[currentIdx]] = [newOrder[currentIdx], newOrder[lockedCount]]; setOrder(newOrder); } setLockedCount((c) => c + 1); }, [hintUsed, completed, onHintUse, nodes, lockedCount, order]); /* Arrow connector between items */ const Arrow = ({ idx }: { idx: number }) => { const isWrong = wrongIndices.includes(idx) || wrongIndices.includes(idx + 1); return (
); }; return (
{/* ── Stage header ── */}
{stageIndex + 1}

Stage {stageIndex + 1} of {totalStages}

{topic}

{description}

{/* ── Instruction ── */}
🔗

Drag to reorder the steps into the correct sequence

{/* ── Chain list (Reorder) ── */}
{ if (completed) return; // Don't allow reordering locked items const locked = order.slice(0, lockedCount); const reorderUnlocked = newOrder.filter((n) => !locked.includes(n)); setOrder([...locked, ...reorderUnlocked]); }} className="flex flex-col" > {order.map((node, idx) => { const isLocked = idx < lockedCount; const isWrong = wrongIndices.includes(idx) && result === "wrong"; const isCorrectResult = result === "correct"; const colorIdx = idx % STEP_COLORS.length; return ( {/* Step number badge */}
{idx + 1}
{/* Label */} {node} {/* Drag handle / lock icon */} {isLocked ? ( ) : !completed ? ( ) : ( )}
{/* Arrow between items */} {idx < order.length - 1 && }
); })}
{/* ── Feedback text ── */} {result === "correct" && (

{feedbackMsg.success}

)} {result === "wrong" && (

{feedbackMsg.error}

)}
{/* spacer */}
{/* ── Bottom bar ── */}
{completed ? "CORRECT ✓" : "CHECK"}
); }