/**
 * Settings Service
 *
 * Server-side key-value settings persisted to SQLite.
 * Used for workspace-wide configuration that needs to be accessible server-side,
 * such as default executable paths for agent providers.
 *
 * Settings are stored in the `settings` table as JSON-encoded values.
 */
import { createLogger } from '../utils/logger.js';
const logger = createLogger('settings-service');
/**
 * Well-known setting keys
 */
export const SETTING_KEYS = {
    AGENT_DEFAULTS: 'agentDefaults',
    RATE_LIMITS: 'rateLimits',
    EXTERNAL_SYNC: 'externalSync',
};
// ============================================================================
// Implementation
// ============================================================================
function dbToSetting(row) {
    let parsedValue;
    try {
        parsedValue = JSON.parse(row.value);
    }
    catch {
        parsedValue = row.value;
    }
    return {
        key: row.key,
        value: parsedValue,
        updatedAt: row.updated_at,
    };
}
const DEFAULT_AGENT_DEFAULTS = {
    defaultExecutablePaths: {},
};
const DEFAULT_EXTERNAL_SYNC_SETTINGS = {
    providers: {},
    syncCursors: {},
    pollIntervalMs: 60000,
    defaultDirection: 'bidirectional',
};
export function createSettingsService(storage) {
    return {
        getSetting(key) {
            const row = storage.queryOne('SELECT key, value, updated_at FROM settings WHERE key = ?', [key]);
            if (!row)
                return undefined;
            return dbToSetting(row);
        },
        setSetting(key, value) {
            const jsonValue = JSON.stringify(value);
            const updatedAt = new Date().toISOString();
            storage.run('INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at', [key, jsonValue, updatedAt]);
            logger.debug(`Setting updated: ${key}`);
            return {
                key,
                value,
                updatedAt,
            };
        },
        deleteSetting(key) {
            const result = storage.run('DELETE FROM settings WHERE key = ?', [key]);
            return result.changes > 0;
        },
        getAgentDefaults() {
            const setting = this.getSetting(SETTING_KEYS.AGENT_DEFAULTS);
            if (!setting) {
                return { ...DEFAULT_AGENT_DEFAULTS };
            }
            // Validate shape — ensure defaultExecutablePaths exists and is an object
            const value = setting.value;
            const paths = value?.defaultExecutablePaths;
            const result = {
                defaultExecutablePaths: paths && typeof paths === 'object' && !Array.isArray(paths)
                    ? paths
                    : {},
            };
            // Include fallbackChain if it's a valid array
            if (Array.isArray(value?.fallbackChain)) {
                result.fallbackChain = value.fallbackChain.filter((entry) => typeof entry === 'string');
            }
            return result;
        },
        setAgentDefaults(defaults) {
            // Validate that defaultExecutablePaths is a plain object of strings
            const paths = defaults.defaultExecutablePaths ?? {};
            const sanitized = {};
            for (const [provider, path] of Object.entries(paths)) {
                if (typeof path === 'string') {
                    sanitized[provider] = path;
                }
            }
            const validated = {
                defaultExecutablePaths: sanitized,
            };
            // Validate fallbackChain — must be an array; filter out non-string entries
            if (Array.isArray(defaults.fallbackChain)) {
                validated.fallbackChain = defaults.fallbackChain.filter((entry) => typeof entry === 'string');
            }
            this.setSetting(SETTING_KEYS.AGENT_DEFAULTS, validated);
            return validated;
        },
        getExternalSyncSettings() {
            const setting = this.getSetting(SETTING_KEYS.EXTERNAL_SYNC);
            if (!setting) {
                return { ...DEFAULT_EXTERNAL_SYNC_SETTINGS, providers: {}, syncCursors: {} };
            }
            const value = setting.value;
            // Validate and sanitize providers
            const rawProviders = value?.providers;
            const providers = {};
            if (rawProviders && typeof rawProviders === 'object' && !Array.isArray(rawProviders)) {
                for (const [name, config] of Object.entries(rawProviders)) {
                    if (config && typeof config === 'object' && !Array.isArray(config)) {
                        const pc = config;
                        providers[name] = {
                            provider: typeof pc.provider === 'string' ? pc.provider : name,
                            token: typeof pc.token === 'string' ? pc.token : undefined,
                            apiBaseUrl: typeof pc.apiBaseUrl === 'string' ? pc.apiBaseUrl : undefined,
                            defaultProject: typeof pc.defaultProject === 'string' ? pc.defaultProject : undefined,
                        };
                    }
                }
            }
            // Validate and sanitize syncCursors
            const rawCursors = value?.syncCursors;
            const syncCursors = {};
            if (rawCursors && typeof rawCursors === 'object' && !Array.isArray(rawCursors)) {
                for (const [key, val] of Object.entries(rawCursors)) {
                    if (typeof val === 'string') {
                        syncCursors[key] = val;
                    }
                }
            }
            return {
                providers,
                syncCursors,
                pollIntervalMs: typeof value?.pollIntervalMs === 'number' ? value.pollIntervalMs : DEFAULT_EXTERNAL_SYNC_SETTINGS.pollIntervalMs,
                defaultDirection: isValidSyncDirection(value?.defaultDirection) ? value.defaultDirection : DEFAULT_EXTERNAL_SYNC_SETTINGS.defaultDirection,
            };
        },
        setExternalSyncSettings(settings) {
            const validated = {
                providers: settings.providers ?? {},
                syncCursors: settings.syncCursors ?? {},
                pollIntervalMs: typeof settings.pollIntervalMs === 'number' && settings.pollIntervalMs > 0
                    ? settings.pollIntervalMs
                    : DEFAULT_EXTERNAL_SYNC_SETTINGS.pollIntervalMs,
                defaultDirection: isValidSyncDirection(settings.defaultDirection)
                    ? settings.defaultDirection
                    : DEFAULT_EXTERNAL_SYNC_SETTINGS.defaultDirection,
            };
            this.setSetting(SETTING_KEYS.EXTERNAL_SYNC, validated);
            return validated;
        },
        getProviderConfig(provider) {
            const settings = this.getExternalSyncSettings();
            return settings.providers[provider];
        },
        setProviderConfig(provider, config) {
            const settings = this.getExternalSyncSettings();
            const sanitized = {
                provider: typeof config.provider === 'string' ? config.provider : provider,
                token: typeof config.token === 'string' ? config.token : undefined,
                apiBaseUrl: typeof config.apiBaseUrl === 'string' ? config.apiBaseUrl : undefined,
                defaultProject: typeof config.defaultProject === 'string' ? config.defaultProject : undefined,
            };
            settings.providers[provider] = sanitized;
            this.setExternalSyncSettings(settings);
            return sanitized;
        },
    };
}
/**
 * Type guard for valid sync direction values
 */
function isValidSyncDirection(value) {
    return value === 'push' || value === 'pull' || value === 'bidirectional';
}
//# sourceMappingURL=settings-service.js.map