///
import {
type Cell,
cell,
Default,
derive,
handler,
lift,
recipe,
str,
toSchema,
} from "commontools";
interface DerivedDifferenceArgs {
primary: Default;
secondary: Default;
primaryStep: Default;
secondaryStep: Default;
}
type AdjustmentDirection = "increase" | "decrease";
type DifferenceSource = "primary" | "secondary";
interface AdjustmentEvent {
amount?: number;
direction?: AdjustmentDirection;
}
interface DifferenceAudit {
sequence: number;
via: DifferenceSource;
primary: number;
secondary: number;
difference: number;
}
const sanitizeInteger = (value: unknown, fallback = 0): number => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return fallback;
}
return Math.trunc(value);
};
const sanitizeStep = (value: unknown, fallback: number): number => {
const raw = sanitizeInteger(value, fallback);
const normalized = Math.abs(raw);
if (normalized === 0) {
return Math.abs(fallback) || 1;
}
return normalized;
};
const resolveDelta = (
event: AdjustmentEvent | number | undefined,
fallback: number,
): number => {
if (typeof event === "number") {
return sanitizeInteger(event, fallback);
}
if (typeof event?.amount === "number") {
return sanitizeInteger(event.amount, fallback);
}
if (event?.direction === "decrease") {
return -fallback;
}
if (event?.direction === "increase") {
return fallback;
}
return fallback;
};
const recordDifference = (
state: {
sequence: Cell;
log: Cell;
history: Cell;
primary: Cell;
secondary: Cell;
},
via: DifferenceSource,
): void => {
const sequence = sanitizeInteger(state.sequence.get(), 0) + 1;
state.sequence.set(sequence);
const primaryValue = sanitizeInteger(state.primary.get(), 0);
const secondaryValue = sanitizeInteger(state.secondary.get(), 0);
const difference = primaryValue - secondaryValue;
const entry: DifferenceAudit = {
sequence,
via,
primary: primaryValue,
secondary: secondaryValue,
difference,
};
state.log.push(entry);
state.history.push(difference);
};
const makeAdjustHandler = (via: DifferenceSource) =>
handler(
(
event: AdjustmentEvent | number | undefined,
context: {
target: Cell;
step: Cell;
primary: Cell;
secondary: Cell;
sequence: Cell;
log: Cell;
history: Cell;
},
) => {
const step = sanitizeStep(context.step.get(), 1);
const delta = resolveDelta(event, step);
const current = sanitizeInteger(context.target.get(), 0);
context.target.set(current + delta);
recordDifference(
{
sequence: context.sequence,
log: context.log,
history: context.history,
primary: context.primary,
secondary: context.secondary,
},
via,
);
},
);
const setStep = handler(
(
event: { step?: number } | number | undefined,
context: { step: Cell },
) => {
const fallback = sanitizeStep(context.step.get(), 1);
const raw = typeof event === "number"
? event
: typeof event?.step === "number"
? event.step
: fallback;
const sanitized = sanitizeStep(raw, fallback);
context.step.set(sanitized);
},
);
export const counterWithDerivedDifference = recipe(
"Counter With Derived Difference",
({ primary, secondary, primaryStep, secondaryStep }) => {
const sequence = cell(0);
const differenceHistory = cell([], { type: "array" });
const auditLog = cell([], { type: "array" });
const primaryValue = lift((value: number | undefined) =>
sanitizeInteger(value, 0)
)(primary);
const secondaryValue = lift((value: number | undefined) =>
sanitizeInteger(value, 0)
)(secondary);
const primaryStepValue = lift((value: number | undefined) =>
sanitizeStep(value, 1)
)(primaryStep);
const secondaryStepValue = lift((value: number | undefined) =>
sanitizeStep(value, 1)
)(secondaryStep);
const differenceSummary = lift(
toSchema<{ primary: Cell; secondary: Cell }>(),
toSchema<{ primary: number; secondary: number; difference: number }>(),
({ primary, secondary }) => {
const primaryValue = sanitizeInteger(primary.get(), 0);
const secondaryValue = sanitizeInteger(secondary.get(), 0);
return {
primary: primaryValue,
secondary: secondaryValue,
difference: primaryValue - secondaryValue,
};
},
)({
primary: primaryValue,
secondary: secondaryValue,
});
const differenceValue = derive(
differenceSummary,
(snapshot) => snapshot.difference,
);
const summaryLabel =
str`Difference ${differenceValue} (primary ${primaryValue}, secondary ${secondaryValue})`;
return {
primaryValue,
secondaryValue,
primaryStepValue,
secondaryStepValue,
differenceValue,
differenceSummary,
summaryLabel,
differenceHistory,
auditLog,
controls: {
primary: {
adjust: makeAdjustHandler("primary")({
target: primary,
step: primaryStep,
primary,
secondary,
sequence,
log: auditLog,
history: differenceHistory,
}),
setStep: setStep({ step: primaryStep }),
},
secondary: {
adjust: makeAdjustHandler("secondary")({
target: secondary,
step: secondaryStep,
primary,
secondary,
sequence,
log: auditLog,
history: differenceHistory,
}),
setStep: setStep({ step: secondaryStep }),
},
},
};
},
);