import { z } from 'zod' const PriorityFilter = z.union([ z.number().int().min(1).max(4), z.literal('all'), ]) const SortDir = z.enum(['asc', 'desc']) const SprintBacklogPrefs = z.object({ filterPriority: PriorityFilter.optional(), filterStatus: z.enum(['OPEN', 'IN_SPRINT', 'DONE', 'all']).optional(), sort: z.enum(['priority', 'status', 'code']).optional(), sortDir: SortDir.optional(), collapsedPbis: z.array(z.string()).optional(), filterPopoverOpen: z.boolean().optional(), }).strict() const PbiListPrefs = z.object({ sort: z.enum(['priority', 'code', 'date']).optional(), filterPriority: PriorityFilter.optional(), filterStatus: z.enum(['ready', 'blocked', 'done', 'all']).optional(), sortDir: SortDir.optional(), }).strict() const StoryPanelPrefs = z.object({ sort: z.enum(['priority', 'code', 'date']).optional(), }).strict() const JobsColumnPrefs = z.object({ kinds: z.array(z.string()), statuses: z.array(z.string()), }).strict() const ViewsPrefs = z.object({ sprintBacklog: SprintBacklogPrefs.optional(), pbiList: PbiListPrefs.optional(), storyPanel: StoryPanelPrefs.optional(), jobsColumns: z.record(z.string(), JobsColumnPrefs).optional(), }).strict() const DevToolsPrefs = z.object({ debugMode: z.boolean().optional(), }).strict() const LayoutPrefs = z.object({ splitPanePositions: z.record(z.string(), z.array(z.number())).optional(), activeSprints: z.record(z.string(), z.string()).optional(), }).strict() export const UserSettingsSchema = z.object({ views: ViewsPrefs.optional(), devTools: DevToolsPrefs.optional(), layout: LayoutPrefs.optional(), }).strict() export type UserSettings = z.infer export const DEFAULT_USER_SETTINGS: UserSettings = {} function isPlainObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) } export function mergeSettings( prev: UserSettings, patch: Partial, ): UserSettings { const out: Record = { ...prev } for (const [key, patchValue] of Object.entries(patch)) { if (patchValue === undefined) continue const prevValue = (prev as Record)[key] if (isPlainObject(patchValue) && isPlainObject(prevValue)) { out[key] = mergeSettings( prevValue as UserSettings, patchValue as Partial, ) } else { out[key] = patchValue } } return out as UserSettings } export function parseUserSettings(raw: unknown): UserSettings { if (raw === null || raw === undefined) return DEFAULT_USER_SETTINGS const result = UserSettingsSchema.safeParse(raw) return result.success ? result.data : DEFAULT_USER_SETTINGS }