import type { ReloadConfigStrictResult } from '../../config'; import { applyConfigSettingsPatchToContent } from '../../config/settings/jsonc-edit'; import type { ConfigValidationWarning, ResolvedConfig } from '../../types/config'; import type { ConfigSettingsPatch, ConfigSettingsSaveResult, ConfigSettingsSnapshot, } from '../../types/settings'; export interface ConfigSettingsHotReloadDiff { hotReloadFields: string[]; restartRequiredFields: string[]; } export interface ConfigSettingsSaveDeps { getConfigPath(): string; getCurrentConfig(): ResolvedConfig; getWarnings(): ConfigValidationWarning[]; getSnapshot(): ConfigSettingsSnapshot; fileExists(path: string): boolean; readText(path: string): string; writeTextAtomically(path: string, content: string): void; deleteFile?(path: string): void; reloadConfigStrict(): ReloadConfigStrictResult; classifyDiff(prev: ResolvedConfig, next: ResolvedConfig): ConfigSettingsHotReloadDiff; getRestartRequiredSections(restartRequiredFields: string[]): string[]; onHotReloadApplied?: (diff: ConfigSettingsHotReloadDiff, config: ResolvedConfig) => void; } export function createSaveConfigSettingsPatchHandler(deps: ConfigSettingsSaveDeps) { return (patch: ConfigSettingsPatch): ConfigSettingsSaveResult => { if (patch.operations.length === 0) { return { ok: true, snapshot: deps.getSnapshot(), hotReloadFields: [], restartRequiredFields: [], restartRequiredSections: [], }; } const configPath = deps.getConfigPath(); const previousConfig = deps.getCurrentConfig(); const previousWarnings = deps.getWarnings(); const hadExistingConfig = deps.fileExists(configPath); const content = hadExistingConfig ? deps.readText(configPath) : '{}\n'; const candidate = applyConfigSettingsPatchToContent({ content, operations: patch.operations, previousWarnings, }); if (!candidate.ok) { return { ok: false, warnings: candidate.warnings, error: candidate.error, hotReloadFields: [], restartRequiredFields: [], restartRequiredSections: [], }; } deps.writeTextAtomically(configPath, candidate.content); const reloadResult = deps.reloadConfigStrict(); if (!reloadResult.ok) { try { if (hadExistingConfig) { deps.writeTextAtomically(configPath, content); } else if (deps.deleteFile) { deps.deleteFile(configPath); } else { deps.writeTextAtomically(configPath, content); } deps.reloadConfigStrict(); } catch { // Best-effort rollback; preserve original reload error for caller. } return { ok: false, warnings: [], error: reloadResult.error, hotReloadFields: [], restartRequiredFields: [], restartRequiredSections: [], }; } const diff = deps.classifyDiff(previousConfig, reloadResult.config); if (diff.hotReloadFields.length > 0) { deps.onHotReloadApplied?.(diff, reloadResult.config); } return { ok: true, snapshot: deps.getSnapshot(), warnings: reloadResult.warnings, hotReloadFields: diff.hotReloadFields, restartRequiredFields: diff.restartRequiredFields, restartRequiredSections: deps.getRestartRequiredSections(diff.restartRequiredFields), }; }; }