"use client";
import { useState, useCallback } from "react";
import { Card } from "@/components/ui/card";
import { StepControls } from "./step-controls";
import { MathDisplay } from "@/components/math/math-display";
import { simplify, toKatex, gcd } from "@/lib/math/fractions";
type ConvertMode = "decToFrac" | "fracToDec";
interface Step {
label: string;
math: string;
gridFilled?: number; // out of 100 for decimal grid
barFilled?: number;
barTotal?: number;
}
function buildDecToFracSteps(decimal: string): Step[] {
const steps: Step[] = [];
const val = parseFloat(decimal);
steps.push({
label: `Convert ${decimal} to a fraction`,
math: `${decimal} = \\;?`,
gridFilled: Math.round(val * 100),
});
// Count decimal places
const parts = decimal.split(".");
const decPlaces = parts[1]?.length || 0;
const denominator = Math.pow(10, decPlaces);
const numerator = Math.round(val * denominator);
steps.push({
label: `${decPlaces} decimal place${decPlaces !== 1 ? "s" : ""} → denominator is ${denominator}`,
math: `${decimal} = ${toKatex(numerator, denominator)}`,
gridFilled: Math.round(val * 100),
barFilled: numerator,
barTotal: denominator,
});
// Simplify
const [sn, sd] = simplify(numerator, denominator);
if (sn !== numerator || sd !== denominator) {
const g = gcd(numerator, denominator);
steps.push({
label: `Simplify by dividing by GCD = ${g}`,
math: `${toKatex(numerator, denominator)} = ${toKatex(sn, sd)}`,
gridFilled: Math.round(val * 100),
barFilled: sn,
barTotal: sd,
});
}
steps.push({
label: `Result: ${decimal} = ${sn}/${sd}`,
math: `${decimal} = ${toKatex(sn, sd)}`,
gridFilled: Math.round(val * 100),
barFilled: sn,
barTotal: sd,
});
return steps;
}
function buildFracToDecSteps(num: number, den: number): Step[] {
const steps: Step[] = [];
steps.push({
label: `Convert ${num}/${den} to a decimal`,
math: `${toKatex(num, den)} = \\;?`,
barFilled: num,
barTotal: den,
});
// Step: Perform the division
const result = num / den;
const resultStr = Number.isInteger(result) ? result.toString() : result.toFixed(6).replace(/0+$/, "");
steps.push({
label: `Divide numerator by denominator`,
math: `${num} \\div ${den} = ${resultStr}`,
barFilled: num,
barTotal: den,
gridFilled: Math.min(100, Math.round(result * 100)),
});
// Check for terminating vs repeating
let tempDen = den;
const [, simpDen] = simplify(num, den);
tempDen = simpDen;
// Remove factors of 2 and 5
while (tempDen % 2 === 0) tempDen /= 2;
while (tempDen % 5 === 0) tempDen /= 5;
const isTerminating = tempDen === 1;
steps.push({
label: isTerminating
? `This is a terminating decimal`
: `This is a recurring decimal`,
math: `${toKatex(num, den)} = ${resultStr}${!isTerminating ? "..." : ""}`,
barFilled: num,
barTotal: den,
gridFilled: Math.min(100, Math.round(result * 100)),
});
return steps;
}
function DecimalGrid({ filled }: { filled: number }) {
return (
{Array.from({ length: 100 }, (_, i) => (
))}
);
}
function FractionBarSmall({ filled, total }: { filled: number; total: number }) {
const segments = Math.min(total, 20);
const fillCount = Math.min(filled, segments);
return (
{Array.from({ length: segments }, (_, i) => (
))}
);
}
export function ConversionExplorer() {
const [mode, setMode] = useState("decToFrac");
const [decInput, setDecInput] = useState("0.75");
const [numInput, setNumInput] = useState("3");
const [denInput, setDenInput] = useState("8");
const [error, setError] = useState("");
const [steps, setSteps] = useState(null);
const [currentStep, setCurrentStep] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const done = steps ? currentStep >= steps.length - 1 : false;
function handleGo() {
setError("");
try {
let s: Step[];
if (mode === "decToFrac") {
const val = parseFloat(decInput);
if (isNaN(val) || val < 0 || val >= 10) {
setError("Enter a valid decimal between 0 and 10.");
return;
}
s = buildDecToFracSteps(decInput.trim());
} else {
const n = parseInt(numInput);
const d = parseInt(denInput);
if (isNaN(n) || isNaN(d) || d === 0) {
setError("Enter a valid fraction (denominator ≠ 0).");
return;
}
s = buildFracToDecSteps(n, d);
}
setSteps(s);
setCurrentStep(0);
setIsPlaying(false);
} catch {
setError("Could not compute. Check your input.");
}
}
const stepForward = useCallback(() => {
if (!steps || currentStep >= steps.length - 1) return;
setCurrentStep((s) => s + 1);
if (currentStep + 1 >= steps.length - 1) setIsPlaying(false);
}, [steps, currentStep]);
const stepBack = useCallback(() => {
if (currentStep <= 0) return;
setCurrentStep((s) => s - 1);
}, [currentStep]);
const togglePlay = useCallback(() => setIsPlaying((p) => !p), []);
const reset = useCallback(() => {
setSteps(null);
setCurrentStep(0);
setIsPlaying(false);
}, []);
const step = steps ? steps[currentStep] : null;
return (
{/* Mode tabs */}
{/* Input */}
{mode === "decToFrac" ? "Enter a decimal" : "Enter a fraction"}
{mode === "decToFrac" ? (
setDecInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleGo()}
placeholder="e.g. 0.75"
autoComplete="off"
className="max-w-[180px] rounded-lg border-2 border-border bg-surface px-3.5 py-2.5 text-lg font-medium outline-none transition-colors focus:border-unit-3"
/>
) : (
)}
{error && {error}
}
{/* Display */}
{!step ? (
Enter a value above and click Convert
) : (
<>
{step.label}
{step.gridFilled !== undefined && (
{step.gridFilled}/100
)}
{step.barFilled !== undefined && step.barTotal !== undefined && (
{step.barFilled}/{step.barTotal}
)}
>
)}
{/* Controls */}
0}
/>
{/* Result */}
{!done || !steps ? (
Result will appear here when steps are complete
) : (
<>
Answer
>
)}
);
}