/// import { type Cell, cell, Default, handler, lift, recipe, str, } from "commontools"; type ChannelId = "email" | "sms" | "push" | "digest"; type Frequency = "immediate" | "hourly" | "daily" | "weekly"; interface ChannelPreference { channel: ChannelId; enabled: boolean; frequency: Frequency; } type ChannelPreferenceInput = Partial & { channel?: string; frequency?: string; }; const channelOrder: readonly ChannelId[] = [ "email", "sms", "push", "digest", ]; const channelLabels: Record = { email: "Email", sms: "SMS", push: "Push", digest: "Digest", }; const frequencyDetails: Record = { immediate: { label: "Immediate alerts", window: "sent instantly", }, hourly: { label: "Hourly updates", window: "top of every hour", }, daily: { label: "Daily summary", window: "08:00 local time", }, weekly: { label: "Weekly digest", window: "Mondays 09:00", }, }; const defaultPreferences: ChannelPreference[] = [ { channel: "email", enabled: true, frequency: "daily" }, { channel: "sms", enabled: false, frequency: "weekly" }, { channel: "push", enabled: true, frequency: "immediate" }, { channel: "digest", enabled: true, frequency: "daily" }, ]; interface NotificationPreferenceArgs { channels: Default; } interface ConfigureChannelEvent { channel?: string; enabled?: boolean; frequency?: string; } const resolveChannelId = (value: unknown): ChannelId | null => { if (typeof value !== "string") return null; const lower = value.toLowerCase(); return channelOrder.find((channel) => channel === lower) ?? null; }; const sanitizeFrequency = ( value: unknown, fallback: Frequency, ): Frequency => { if (typeof value !== "string") return fallback; const lower = value.toLowerCase() as Frequency; return lower in frequencyDetails ? lower : fallback; }; const sanitizePreferenceList = ( value: readonly ChannelPreferenceInput[] | undefined, ): ChannelPreference[] => { const base = new Map(); for (const entry of defaultPreferences) { base.set(entry.channel, { ...entry }); } if (Array.isArray(value)) { for (const candidate of value) { const channel = resolveChannelId(candidate?.channel); if (!channel) continue; const previous = base.get(channel) ?? { channel, enabled: true, frequency: "daily" as Frequency, }; const enabled = typeof candidate?.enabled === "boolean" ? candidate.enabled : previous.enabled; const frequency = sanitizeFrequency( candidate?.frequency, previous.frequency, ); base.set(channel, { channel, enabled, frequency }); } } return channelOrder.map((channel) => { const preference = base.get(channel); return preference ? { ...preference } : { channel, enabled: false, frequency: "daily", }; }); }; const buildScheduleMap = ( entries: readonly ChannelPreference[], ): Record => { const result = {} as Record; for (const preference of entries) { if (!preference.enabled) { result[preference.channel] = "paused"; continue; } const detail = frequencyDetails[preference.frequency]; result[preference.channel] = `${detail.label} (${detail.window})`; } return result; }; const formatActiveSummary = ( entries: readonly ChannelPreference[], ): string => { const active = entries.filter((entry) => entry.enabled); if (active.length === 0) return "No active channels"; const pieces = active.map((entry) => { const label = channelLabels[entry.channel]; const detail = frequencyDetails[entry.frequency]; return `${label} ${detail.label.toLowerCase()}`; }); const noun = active.length === 1 ? "channel" : "channels"; return `${active.length} active ${noun}: ${pieces.join(", ")}`; }; const configureChannel = handler( ( event: ConfigureChannelEvent | undefined, context: { channels: Cell; lastChange: Cell; history: Cell; sequence: Cell; }, ) => { const channel = resolveChannelId(event?.channel); if (!channel) return; const current = sanitizePreferenceList(context.channels.get()); const existing = current.find((entry) => entry.channel === channel); if (!existing) return; const enabled = typeof event?.enabled === "boolean" ? event.enabled : existing.enabled; const frequency = sanitizeFrequency(event?.frequency, existing.frequency); const updated = current.map((entry) => entry.channel === channel ? { channel, enabled, frequency } : entry ); context.channels.set(updated); const detail = frequencyDetails[frequency]; const summary = enabled ? `${channelLabels[channel]} ${detail.label} (${detail.window})` : `${channelLabels[channel]} paused`; context.lastChange.set(summary); const previous = context.history.get() ?? []; const appended = [...previous, summary]; const trimmed = appended.length > 5 ? appended.slice(-5) : appended; context.history.set(trimmed); const sequence = (context.sequence.get() ?? 0) + 1; context.sequence.set(sequence); }, ); export const notificationPreferences = recipe( "Notification Preferences", ({ channels }) => { const lastChange = cell("Preferences loaded"); const history = cell(["Preferences loaded"]); const sequence = cell(0); const channelList = lift(sanitizePreferenceList)(channels); const scheduleMap = lift(buildScheduleMap)(channelList); const summaryBase = lift(formatActiveSummary)(channelList); const scheduleSummary = str`Notification schedules — ${summaryBase}`; const activeCount = lift((entries: readonly ChannelPreference[]) => entries.filter((entry) => entry.enabled).length )(channelList); return { channels, channelList, scheduleMap, scheduleSummary, activeCount, lastChange, history, configureChannel: configureChannel({ channels, lastChange, history, sequence, }), }; }, );