import { useMemo, useState } from "react"; import ModuleSlots from "./ModuleSlots"; import styles from "./ClockWizard.module.css"; import { useClockStore } from "../../store/useClockStore"; import type { Recipe } from "../../../scripts/factorio-dump/helpers/recipes.helper"; import type { Machine, Module } from "../../../scripts/factorio-dump/process-data.models"; import MachineSelector from "../MachineSelector"; import BeaconConfigurator, { type BeaconGroup } from "./BeaconConfigurator"; import ExpressionInput from "./ExpressionInpux"; import InputConfigurator from "./InputConfigurator"; import { calculateOptimalBatch, computeMachineStats, generateAdvancedClock, type ClockConfig } from "../../engine"; export default function ClockWizard() { const loadState = useClockStore((s) => s.loadState); const [recipe, setRecipe] = useState(null); const [machine, setMachine] = useState(null); const [machineQuality, setMachineQuality] = useState(0); const [machineModules, setMachineModules] = useState<{ module: Module; qualityLevel: number }[]>([]); const [beaconGroups, setBeaconGroups] = useState([]); // Storing user preferences for inserter mapping (mixed belts, presets) const [inputConfigs, setInputConfigs] = useState>({}); // Target Throughput State const [targetThroughput, setTargetThroughput] = useState(0); // --- LIVE STATS CALCULATION --- const stats = useMemo(() => { if (!machine || !recipe) return null; return computeMachineStats( { machine, machineQualityLevel: machineQuality, machineModules, beacons: beaconGroups.map((bg) => ({ beacon: bg.beacon, beaconQualityLevel: bg.qualityLevel, count: bg.count, modules: bg.modules, })), }, recipe, ); }, [machine, recipe, machineQuality, machineModules, beaconGroups]); // --- THROUGHPUT & SCALING MATH --- const throughputData = useMemo(() => { if (!stats || !recipe || !recipe.results) return null; // Find main output const mainResult = recipe.results[0]; const baseAmount = (mainResult as any).amount ?? (mainResult as any).amount_min ?? 1; const yieldPerCraft = baseAmount * (1 + stats.productivityBonus); // Items per minute for ONE machine const baseItemsPerMin = yieldPerCraft * stats.craftsPerSecond * 60; // How many machines are needed? const requiredMachines = targetThroughput > 0 ? Math.ceil(targetThroughput / baseItemsPerMin) : 1; return { mainOutputName: mainResult.name, baseItemsPerMin, requiredMachines, actualThroughput: requiredMachines * baseItemsPerMin, }; }, [stats, recipe, targetThroughput]); const handleGenerate = () => { if (!recipe || !machine || !stats || !throughputData) return; // Math const batch = calculateOptimalBatch(recipe, stats, {}); // Build the strict Config Interface const clockConfig: ClockConfig = { machineCount: throughputData.requiredMachines, inputs: {}, outputs: {}, }; // Map UI input configs to the Engine contract Object.keys(batch.inputs).forEach((itemId) => { const userCfg = inputConfigs[itemId] || {}; clockConfig.inputs[itemId] = { inserterId: userCfg.inserterId || `in-${itemId}`, presetId: userCfg.source === "belt" ? "belt_to_chest" : "chest_to_chest", swingTicks: userCfg.source === "belt" ? 12 : 8, stackSize: 16, }; }); // Default output configs (could also be exposed in UI later) Object.keys(batch.outputs).forEach((itemId) => { clockConfig.outputs[itemId] = { inserterId: `out-${itemId}`, presetId: "chest_to_belt", swingTicks: 12, stackSize: 16, }; }); // 3. Generate Blueprint / Timeline Blocks const clockData = generateAdvancedClock(batch, clockConfig); // 4. Update Store loadState({ duration: clockData.duration, rows: Object.fromEntries(clockData.rows.map((r) => [r.id, r])), rowOrder: clockData.rows.map((r) => r.id), blocks: Object.fromEntries(clockData.blocks.map((b) => [b.id, b])), selectedBlockIds: new Set(), }); }; return (
{/* Recipe & Machine */}

1. Setup Machine

{ setMachine(res.machine); setMachineQuality(res.qualityLevel); setRecipe(res.recipe); }} />
{/* --- STATS PREVIEW DASHBOARD --- */} {stats && throughputData && (

Live Capabilities

{/* Target Throughput Input */}
setTargetThroughput(value)} className={styles.input} >
Machine Output
{Math.round(throughputData.baseItemsPerMin)}{" "} / min ({Math.round(throughputData.baseItemsPerMin / 6) / 10} / sec)
Machines Needed
{throughputData.requiredMachines} {targetThroughput > 0 && ( ({Math.round(throughputData.actualThroughput)}/m) )}
Craft Time (Ticks)
{stats.singleCraftTicks.toFixed(1)}t
Overload Limit
{stats.overloadMultiplier}x
{stats.singleCraftTicks}
)}
{machine && recipe && ( <> {/* Modules & Beacons */}

2. Machine Modules

3. Beacons

{/* Input Configurator (Mixed Belts) */}

4. Route Inputs (Mixed Belts)

)}
); }