settings.jsjavascript
/**
 * @file Persistent settings store. All user-configurable settings (mirrors
 * src/partials/settings.html) persist to a single settings.json in the OS app-data
 * directory: ~/Library/Application Support/<AppName>/ on macOS, %APPDATA%\<AppName>\ on
 * Windows, ~/.config/<AppName>/ on Linux. app.getPath('userData') resolves the correct
 * per-OS, per-app location, so neither a path nor the app name is ever hardcoded. The
 * file lives OUTSIDE the source tree, so it survives app updates and isn't synced by
 * Dropbox/iCloud alongside the code.
 *
 * What does NOT belong here: OAuth tokens / API keys / passwords. Plaintext secrets must
 * never sit in this JSON — those go through Electron `safeStorage` (backed by the macOS
 * Keychain / Windows Credential Manager) in a separate module. The `integrations` block
 * stores only non-secret connection metadata (a connected flag + display name) so the UI
 * can render state without holding the secret.
 *
 * The default schema, deep-merge rules, and never-throw read / atomic write live in
 * ./settings-store (pure Node); this file binds them to the Electron userData path and
 * keeps the in-memory cache.
 * @module settings
 */

const { app } = require('electron');
const path = require('path');
const store = require('./settings-store');

const DEFAULTS = store.DEFAULTS;

/**
 * Loaded settings, kept in memory after first load. Null until then.
 * @type {?Object}
 */
let cache = null;

/**
 * Absolute path of settings.json in the Electron userData directory.
 * @returns {string} File path.
 */
function filePath() {
  return path.join(app.getPath('userData'), 'settings.json');
}

/**
 * Read settings from disk once, then serve from the in-memory cache. A missing or
 * corrupt file falls back to the defaults — the underlying read never throws.
 * @returns {Object} The full settings object.
 */
function load() {
  if (cache) return cache;
  cache = store.read(filePath());
  return cache;
}

/**
 * Merge a partial patch into the current settings and persist the result. The renderer
 * sends only the slice it changed (e.g. { files: { nameAffix: {...} } }); mergeDefaults
 * folds it onto the cached object so untouched branches are preserved.
 * @param {Object} patch Partial settings object — only the branches to change.
 * @returns {Object} The full merged settings, so the caller can refresh its view in one
 *   round trip.
 */
function update(patch) {
  const current = load();
  cache = store.mergeDefaults(current, patch);
  store.persist(filePath(), cache);
  return cache;
}

module.exports = { load, update, filePath, DEFAULTS };