import type { ConfigSettingsCategory, ConfigSettingsField, ConfigSettingsPatchOperation, ConfigSettingsSnapshotValue, } from '../types/settings'; export interface SettingsFilter { category?: ConfigSettingsCategory; query?: string; } export interface SettingsDraft { readonly initialValues: Record; readonly values: Record; readonly resetPaths: Set; } function normalizeQuery(query: string | undefined): string { return (query ?? '').trim().toLowerCase(); } function searchableText(parts: Array): string { return parts .filter(Boolean) .join(' ') .replace(/([a-z0-9])([A-Z])/g, '$1 $2') .replace(/[^a-zA-Z0-9]+/g, ' ') .toLowerCase(); } function valuesEqual(a: unknown, b: unknown): boolean { return JSON.stringify(a) === JSON.stringify(b); } export function filterSettingsFields( fields: ConfigSettingsField[], filter: SettingsFilter, ): ConfigSettingsField[] { const query = normalizeQuery(filter.query); const terms = query.length > 0 ? searchableText([query]).split(/\s+/).filter(Boolean) : []; return fields.filter((field) => { if (field.legacyHidden || field.settingsHidden) { return false; } if (filter.category && field.category !== filter.category) { return false; } if (!query || terms.length === 0) { return true; } const haystack = searchableText([ field.label, field.description, field.configPath, field.section, field.subsection ?? '', field.enumValues?.join(' ') ?? '', ]); return terms.every((term) => haystack.includes(term)); }); } export function createSettingsDraft( values: Record, ): SettingsDraft { return { initialValues: structuredClone(values), values: structuredClone(values), resetPaths: new Set(), }; } export function setDraftValue( draft: SettingsDraft, path: string, value: ConfigSettingsSnapshotValue, ): void { draft.values[path] = value; draft.resetPaths.delete(path); } export function resetDraftPath(draft: SettingsDraft, path: string, defaultValue: unknown): void { draft.values[path] = structuredClone(defaultValue); draft.resetPaths.add(path); } export function getDirtyOperations(draft: SettingsDraft): ConfigSettingsPatchOperation[] { const operations: ConfigSettingsPatchOperation[] = []; const paths = new Set([...Object.keys(draft.initialValues), ...Object.keys(draft.values)]); for (const path of [...paths].sort()) { if (draft.resetPaths.has(path)) { operations.push({ op: 'reset', path }); continue; } if (!valuesEqual(draft.values[path], draft.initialValues[path])) { operations.push({ op: 'set', path, value: draft.values[path], }); } } return operations; }