///
import {
type Cell,
Default,
derive,
handler,
lift,
recipe,
str,
} from "commontools";
interface OpaqueMapArgs {
value: Default;
history: Default;
labelPrefix: Default;
}
interface RecordEvent {
delta?: number;
}
interface RewriteEvent {
index?: number;
value?: number;
}
const recordValue = handler(
(
event: RecordEvent | undefined,
context: { value: Cell; history: Cell },
) => {
const delta = typeof event?.delta === "number" ? event.delta : 1;
const current = context.value.get() ?? 0;
const next = current + delta;
context.value.set(next);
context.history.push(next);
},
);
const rewriteHistoryEntry = handler(
(
event: RewriteEvent | undefined,
context: { history: Cell },
) => {
if (typeof event?.value !== "number") return;
const targetIndex = typeof event.index === "number" ? event.index : 0;
const values = context.history.get();
if (!Array.isArray(values)) return;
if (targetIndex < 0 || targetIndex >= values.length) return;
const entryCell = context.history.key(targetIndex) as Cell;
entryCell.set(event.value);
},
);
const clampToNumberArray = (entries: number[] | undefined) => {
if (!Array.isArray(entries)) return [] as number[];
return entries.filter((item): item is number => typeof item === "number");
};
export const counterWithOpaqueRefMap = recipe(
"Counter With OpaqueRef Map",
({ value, history, labelPrefix }) => {
const safeHistory = lift(clampToNumberArray)(history);
const labels = safeHistory.map((entry, index) => str`#${index}: ${entry}`);
const count = derive(
history,
(entries) => clampToNumberArray(entries).length,
);
const total = derive(
history,
(entries) =>
clampToNumberArray(entries).reduce((sum, item) => sum + item, 0),
);
const headline = str`${labelPrefix} ${value} (${count} entries)`;
return {
value,
history,
count,
total,
headline,
labels,
record: recordValue({ value, history }),
rewrite: rewriteHistoryEntry({ history }),
};
},
);