"use client"; import { motion } from "framer-motion"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ActivityRenderer } from "@/components/activities"; import { PromptCard, QuestionText } from "@/components/activities/shared"; import { XIcon } from "@/components/icons"; import { Button, ProgressBar } from "@/components/ui"; import { assembleSolution } from "@/lib/homework"; import { useI18n } from "@/lib/i18n"; import type { HomeworkGuideRecord, HomeworkStep } from "@/lib/schemas"; import { useWakeLock } from "@/lib/use-wake-lock"; import { CopyButton } from "./CopyButton"; /** * Mode 2: one step per screen in the same snap-feed as the study feed. * Each step explains what to do, reveals hints one at a time (nudge → hint → * the full answer), and can carry a checkpoint rendered by the shared activity * components. The last screen assembles the solution from the step answers. */ export function HomeworkGuide({ record, onClose, onAgain, onNew, againLoading, }: { record: HomeworkGuideRecord; onClose: () => void; onAgain: () => void; onNew: () => void; againLoading: boolean; }) { const { t } = useI18n(); /* Keep the screen awake while working through the guide. */ useWakeLock(true); const steps = record.steps; const slideCount = steps.length + 1; // + the solution slide const [index, setIndex] = useState(0); const [revealed, setRevealed] = useState>({}); const scrollRef = useRef(null); const goTo = useCallback( (target: number) => { const container = scrollRef.current; if (!container) return; const clamped = Math.max(0, Math.min(target, slideCount - 1)); container.scrollTo({ top: clamped * container.clientHeight, behavior: "smooth" }); }, [slideCount], ); // Track the visible slide (wheel, touch and programmatic scroll). useEffect(() => { const container = scrollRef.current; if (!container) return; let frame = 0; const onScroll = () => { cancelAnimationFrame(frame); frame = requestAnimationFrame(() => { const height = container.clientHeight || 1; const current = Math.round(container.scrollTop / height); setIndex((previous) => (previous === current ? previous : current)); }); }; container.addEventListener("scroll", onScroll, { passive: true }); return () => { container.removeEventListener("scroll", onScroll); cancelAnimationFrame(frame); }; }, []); // Keyboard navigation, like the study feed. useEffect(() => { const onKey = (event: KeyboardEvent) => { const target = event.target as HTMLElement | null; if (target && (/^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName) || target.isContentEditable)) return; if (event.metaKey || event.ctrlKey || event.altKey) return; if (event.key === "ArrowDown" || event.key === "PageDown" || event.key === "j") { event.preventDefault(); goTo(index + 1); } else if (event.key === "ArrowUp" || event.key === "PageUp" || event.key === "k") { event.preventDefault(); goTo(index - 1); } else if (event.key === "Escape") { onClose(); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [goTo, index, onClose]); const solution = useMemo(() => assembleSolution(record), [record]); const progressValue = Math.min(index + 1, slideCount); const onSolution = index >= steps.length; const revealedCount = (stepIndex: number) => revealed[stepIndex] ?? 0; const revealNext = (stepIndex: number, hints: string[]) => setRevealed((current) => ({ ...current, [stepIndex]: Math.min((current[stepIndex] ?? 0) + 1, hints.length), })); const hintButtonLabel = (step: HomeworkStep, stepIndex: number) => { const count = revealedCount(stepIndex); if (count === 0) return t("homework.guide.hint"); if (count === step.hints.length - 1) return t("homework.guide.hintAnswer"); return t("homework.guide.hintMore"); }; return (
{/* chrome ------------------------------------------------------- */}
{record.title} {onSolution ? t("homework.guide.solution") : t("homework.guide.stepOf", { n: index + 1, total: steps.length })}
{/* slides ------------------------------------------------------- */}
{steps.map((step, stepIndex) => (
{t("homework.guide.stepOf", { n: stepIndex + 1, total: steps.length })}
{step.title}

{step.explanation}

{/* progressive hints */}
{step.hints.slice(0, revealedCount(stepIndex)).map((hint, hintIndex) => ( {t("homework.guide.hintN", { n: hintIndex + 1 })}: {hint} ))} {revealedCount(stepIndex) < step.hints.length ? ( ) : null}
{/* checkpoint or a plain next button */} {step.checkpoint ? (

{t("homework.guide.tryIt")}

{ /* checkpoints are for the student, not for scoring */ }} onNext={() => goTo(stepIndex + 1)} />
) : ( )}
))} {/* the assembled solution -------------------------------------- */}
{t("homework.guide.solution")}

{solution}

{t("homework.guide.solutionNote")}

); }