/// import { type Cell, cell, Default, derive, handler, lift, recipe, str, } from "commontools"; interface AlternateInitialStateSeed { id?: unknown; label?: unknown; value?: unknown; step?: unknown; } interface AlternateInitialState { id: string; label: string; value: number; step: number; } interface SelectionLogEntry { id: string; value: number; step: number; reason: string; index: number; } interface AlternateInitialStatesArgs { states: Default< AlternateInitialStateSeed[], typeof defaultInitialStateSeeds >; } interface SelectInitialEvent { id?: unknown; reason?: unknown; } interface IncrementEvent { amount?: unknown; } const defaultInitialStateSeeds = [ { id: "baseline", label: "Baseline", value: 0, step: 1 }, { id: "boost", label: "Momentum Boost", value: 8, step: 3 }, ] satisfies AlternateInitialStateSeed[]; const fallbackState = (): AlternateInitialState => ({ id: "baseline", label: "Baseline", value: 0, step: 1, }); const toFiniteNumber = (input: unknown, fallback: number): number => { if (typeof input !== "number" || !Number.isFinite(input)) { return fallback; } return input; }; const toPositiveStep = (input: unknown, fallback: number): number => { const value = toFiniteNumber(input, fallback); if (value > 0) { return value; } return fallback > 0 ? fallback : 1; }; const toStateId = (input: unknown, fallback: string): string => { if (typeof input !== "string") return fallback; const trimmed = input.trim(); return trimmed.length > 0 ? trimmed : fallback; }; const toLabel = (input: unknown, fallback: string): string => { if (typeof input !== "string") return fallback; const trimmed = input.trim(); return trimmed.length > 0 ? trimmed : fallback; }; const sanitizeStateSeeds = ( seeds: AlternateInitialStateSeed[] | undefined, ): AlternateInitialState[] => { if (!Array.isArray(seeds)) { return [fallbackState()]; } const sanitized: AlternateInitialState[] = []; const seen = new Set(); for (const seed of seeds) { if (!seed || typeof seed !== "object") continue; const provisionalId = toStateId( (seed as { id?: unknown }).id, `state-${sanitized.length + 1}`, ); if (seen.has(provisionalId)) continue; seen.add(provisionalId); const label = toLabel((seed as { label?: unknown }).label, provisionalId); const value = toFiniteNumber( (seed as { value?: unknown }).value, fallbackState().value, ); const step = toPositiveStep( (seed as { step?: unknown }).step, fallbackState().step, ); sanitized.push({ id: provisionalId, label, value, step }); } if (sanitized.length === 0) { sanitized.push(fallbackState()); } return sanitized; }; const applyIncrement = handler( ( event: IncrementEvent | undefined, context: { value: Cell; step: Cell }, ) => { const stepSize = toPositiveStep(context.step.get(), 1); const amount = toFiniteNumber(event?.amount, stepSize); const current = toFiniteNumber(context.value.get(), 0); context.value.set(current + amount); }, ); const selectInitialState = handler( ( event: SelectInitialEvent | undefined, context: { value: Cell; step: Cell; activeId: Cell; states: Cell; log: Cell; }, ) => { const available = context.states.get(); const baseList = Array.isArray(available) && available.length > 0 ? available : [fallbackState()]; const requestedId = toStateId(event?.id, baseList[0].id); const target = baseList.find((entry) => entry.id === requestedId) ?? baseList[0]; context.activeId.set(target.id); context.value.set(target.value); context.step.set(target.step); const existing = context.log.get(); const history = Array.isArray(existing) ? existing.slice() : []; const index = history.length + 1; const entry: SelectionLogEntry = { id: target.id, value: target.value, step: target.step, reason: toLabel(event?.reason, "selectInitial"), index, }; history.push(entry); context.log.set(history); }, ); export const counterWithAlternateInitialStates = recipe< AlternateInitialStatesArgs >("Counter With Alternate Initial States", ({ states }) => { const sanitizedStates = lift(sanitizeStateSeeds)(states); const activeStateId = cell(fallbackState().id); const valueCell = cell(0); const stepCell = cell(1); const selectionLog = cell([]); const activeState = lift( ( input: | { states?: AlternateInitialState[]; active?: Cell; } | undefined, ): AlternateInitialState => { const candidate = Array.isArray(input?.states) ? input?.states?.slice() ?? [] : []; const list = candidate.length > 0 ? candidate : [fallbackState()]; const desiredId = toStateId(input?.active?.get(), list[0].id); return list.find((entry) => entry.id === desiredId) ?? list[0]; }, )({ states: sanitizedStates, active: activeStateId }); const selectionCount = derive( selectionLog, (entries) => Array.isArray(entries.get()) ? entries.get().length : 0, ); const label = str`State ${ activeState.key("label") }=${valueCell} (step ${stepCell})`; return { value: valueCell, step: stepCell, activeStateId, activeState, availableStates: sanitizedStates, label, selectionLog, selectionCount, increment: applyIncrement({ value: valueCell, step: stepCell }), selectInitial: selectInitialState({ value: valueCell, step: stepCell, activeId: activeStateId, states: sanitizedStates, log: selectionLog, }), }; });