/**
 * Settings Type Definitions
 *
 * Types for managing Crewly application settings including
 * runtime preferences, chat settings, and skill configurations.
 *
 * @module types/settings.types
 */
import { CREWLY_AGENT_MANAGED_COMMAND } from '../constants.js';
/**
 * Array of all valid AI runtimes
 */
export const AI_RUNTIMES = [
    'claude-code',
    'gemini-cli',
    'codex-cli',
    'crewly-agent',
];
/**
 * Array of all valid API key providers
 */
export const API_KEY_PROVIDERS = [
    'gemini',
    'anthropic',
    'openai',
    'deepseek',
];
/**
 * Environment variable names for each provider's API key
 */
export const API_KEY_ENV_VARS = {
    gemini: ['GOOGLE_GENERATIVE_AI_API_KEY', 'GEMINI_API_KEY'],
    anthropic: ['ANTHROPIC_API_KEY'],
    openai: ['OPENAI_API_KEY'],
    deepseek: ['DEEPSEEK_API_KEY'],
};
// ============================================================================
// Settings Constants
// ============================================================================
/**
 * Minimum values for numeric settings
 */
export const SETTINGS_CONSTRAINTS = {
    MIN_CHECK_IN_INTERVAL: 1,
    MIN_MAX_CONCURRENT_AGENTS: 1,
    MIN_MAX_MESSAGE_HISTORY: 10,
    MIN_SKILL_EXECUTION_TIMEOUT: 1000,
};
// ============================================================================
// Utility Functions
// ============================================================================
/**
 * Type guard to check if a string is a valid AI runtime
 *
 * @param value - The string to check
 * @returns True if the value is a valid AI runtime
 *
 * @example
 * ```typescript
 * if (isValidAIRuntime(input)) {
 *   // input is typed as AIRuntime
 * }
 * ```
 */
export function isValidAIRuntime(value) {
    return AI_RUNTIMES.includes(value);
}
/**
 * Get default settings with all sensible values
 *
 * @returns Complete Crewly settings with default values
 *
 * @example
 * ```typescript
 * const defaults = getDefaultSettings();
 * console.log(defaults.general.defaultRuntime); // 'claude-code'
 * ```
 */
export function getDefaultSettings() {
    return {
        general: {
            defaultRuntime: 'claude-code',
            autoStartOrchestrator: true,
            autoRecoverHungOrchestrator: true,
            checkInIntervalMinutes: 5,
            maxConcurrentAgents: 10,
            verboseLogging: false,
            autoResumeOnRestart: true,
            announceOnBoot: true,
            runtimeCommands: {
                'claude-code': 'claude --dangerously-skip-permissions',
                'gemini-cli': 'gemini --yolo',
                'codex-cli': 'codex -a never -s danger-full-access',
                // The managed external binary (PR #599). NOT 'crewly-agent-in-process'
                // — that stale sentinel shelled out to a non-existent command and
                // exited 127 (issue #693).
                'crewly-agent': CREWLY_AGENT_MANAGED_COMMAND,
            },
            agentIdleTimeoutMinutes: 30,
            enableProactiveCompact: false,
            enableThresholdCompact: false,
            enableSelfEvolution: false,
            tokenTracking: false,
            enableAuditor: false,
            autonomyTickIntervalMinutes: 5,
        },
        chat: {
            showRawTerminalOutput: false,
            enableTypingIndicator: true,
            maxMessageHistory: 1000,
            autoScrollToBottom: true,
            showTimestamps: true,
        },
        skills: {
            skillsDirectory: '',
            enableBrowserAutomation: true,
            browserProfile: {
                headless: true,
                stealth: false,
                humanDelayMinMs: 300,
                humanDelayMaxMs: 1200,
            },
            enableScriptExecution: true,
            skillExecutionTimeoutMs: 60000,
        },
        apiKeys: {
            global: {},
            runtimeOverrides: {},
            skillOverrides: {},
        },
    };
}
/**
 * Validate settings and return validation result
 *
 * @param settings - The settings to validate
 * @returns Validation result with valid flag and any errors
 *
 * @example
 * ```typescript
 * const result = validateSettings(settings);
 * if (!result.valid) {
 *   console.error('Validation errors:', result.errors);
 * }
 * ```
 */
export function validateSettings(settings) {
    const errors = [];
    // Validate general settings
    if (!isValidAIRuntime(settings.general.defaultRuntime)) {
        errors.push(`Invalid default runtime: ${settings.general.defaultRuntime}`);
    }
    if (typeof settings.general.autoStartOrchestrator !== 'boolean') {
        errors.push('autoStartOrchestrator must be a boolean');
    }
    if (typeof settings.general.autoResumeOnRestart !== 'boolean') {
        errors.push('autoResumeOnRestart must be a boolean');
    }
    if (settings.general.checkInIntervalMinutes < SETTINGS_CONSTRAINTS.MIN_CHECK_IN_INTERVAL) {
        errors.push(`Check-in interval must be at least ${SETTINGS_CONSTRAINTS.MIN_CHECK_IN_INTERVAL} minute`);
    }
    if (settings.general.maxConcurrentAgents < SETTINGS_CONSTRAINTS.MIN_MAX_CONCURRENT_AGENTS) {
        errors.push(`Max concurrent agents must be at least ${SETTINGS_CONSTRAINTS.MIN_MAX_CONCURRENT_AGENTS}`);
    }
    if (typeof settings.general.agentIdleTimeoutMinutes === 'number' && settings.general.agentIdleTimeoutMinutes < 0) {
        errors.push('agentIdleTimeoutMinutes must be >= 0 (0 disables idle suspension)');
    }
    if (typeof settings.general.autonomyTickIntervalMinutes !== 'number' || settings.general.autonomyTickIntervalMinutes < 0) {
        errors.push('autonomyTickIntervalMinutes must be a number >= 0 (0 disables the polling tick — events still fire)');
    }
    if (typeof settings.general.enableProactiveCompact !== 'boolean') {
        errors.push('enableProactiveCompact must be a boolean');
    }
    if (typeof settings.general.enableThresholdCompact !== 'boolean') {
        errors.push('enableThresholdCompact must be a boolean');
    }
    if (typeof settings.general.enableSelfEvolution !== 'undefined' && typeof settings.general.enableSelfEvolution !== 'boolean') {
        errors.push('enableSelfEvolution must be a boolean');
    }
    // Validate runtimeCommands
    if (settings.general.runtimeCommands) {
        for (const runtime of AI_RUNTIMES) {
            const cmd = settings.general.runtimeCommands[runtime];
            if (typeof cmd !== 'string' || cmd.trim().length === 0) {
                errors.push(`Runtime command for ${runtime} must be a non-empty string`);
            }
        }
    }
    // Validate chat settings
    if (settings.chat.maxMessageHistory < SETTINGS_CONSTRAINTS.MIN_MAX_MESSAGE_HISTORY) {
        errors.push(`Max message history must be at least ${SETTINGS_CONSTRAINTS.MIN_MAX_MESSAGE_HISTORY}`);
    }
    // Validate skills settings
    if (settings.skills.skillExecutionTimeoutMs < SETTINGS_CONSTRAINTS.MIN_SKILL_EXECUTION_TIMEOUT) {
        errors.push(`Skill execution timeout must be at least ${SETTINGS_CONSTRAINTS.MIN_SKILL_EXECUTION_TIMEOUT}ms`);
    }
    if (settings.skills.browserProfile) {
        if (typeof settings.skills.browserProfile.headless !== 'boolean') {
            errors.push('skills.browserProfile.headless must be a boolean');
        }
        if (typeof settings.skills.browserProfile.stealth !== 'boolean') {
            errors.push('skills.browserProfile.stealth must be a boolean');
        }
    }
    return {
        valid: errors.length === 0,
        errors,
    };
}
/**
 * Deep merge settings with partial updates
 *
 * Creates a new settings object without modifying the originals.
 *
 * @param existing - The existing settings
 * @param updates - Partial updates to apply
 * @returns New settings object with updates applied
 *
 * @example
 * ```typescript
 * const updated = mergeSettings(current, {
 *   general: { defaultRuntime: 'gemini-cli' },
 * });
 * ```
 */
export function mergeSettings(existing, updates) {
    const existingApiKeys = existing.apiKeys ?? { global: {} };
    const updatedApiKeys = updates.apiKeys;
    // Deep merge runtimeCommands with defaults so new runtimes (e.g. crewly-agent)
    // always have commands even when loaded from old settings files.
    const defaultRuntimeCommands = getDefaultSettings().general.runtimeCommands;
    const mergedRuntimeCommands = {
        ...defaultRuntimeCommands,
        ...existing.general.runtimeCommands,
        ...updates.general?.runtimeCommands,
    };
    return {
        general: { ...existing.general, ...updates.general, runtimeCommands: mergedRuntimeCommands },
        chat: { ...existing.chat, ...updates.chat },
        skills: { ...existing.skills, ...updates.skills },
        apiKeys: updatedApiKeys
            ? {
                global: { ...existingApiKeys.global, ...updatedApiKeys.global },
                runtimeOverrides: { ...existingApiKeys.runtimeOverrides, ...updatedApiKeys.runtimeOverrides },
                skillOverrides: { ...existingApiKeys.skillOverrides, ...updatedApiKeys.skillOverrides },
            }
            : existingApiKeys,
    };
}
/**
 * Check if a partial settings object has any defined values
 *
 * @param settings - The partial settings to check
 * @returns True if any section has defined values
 */
export function hasSettingsUpdates(settings) {
    if (settings.general && Object.keys(settings.general).length > 0) {
        return true;
    }
    if (settings.chat && Object.keys(settings.chat).length > 0) {
        return true;
    }
    if (settings.skills && Object.keys(settings.skills).length > 0) {
        return true;
    }
    if (settings.apiKeys && Object.keys(settings.apiKeys).length > 0) {
        return true;
    }
    return false;
}
/**
 * Check if a string is a valid API key provider
 *
 * @param value - The string to check
 * @returns True if the value is a valid API key provider
 */
export function isValidApiKeyProvider(value) {
    return API_KEY_PROVIDERS.includes(value);
}
/**
 * Mask an API key for safe display, showing only the last 4 characters
 *
 * @param key - The API key to mask
 * @returns Masked key string (e.g. "••••••••abcd")
 */
export function maskApiKey(key) {
    if (!key || key.length <= 4) {
        return '••••';
    }
    return '••••••••' + key.slice(-4);
}
/**
 * Resolve an API key through the override chain: skill → runtime → global → env var
 *
 * @param provider - The API key provider
 * @param apiKeysSettings - The API keys settings
 * @param context - Resolution context with optional runtime and skill
 * @returns The resolved API key or undefined if not found
 */
export function resolveApiKey(provider, apiKeysSettings, context) {
    // 1. Check skill-level override
    if (context?.skill && apiKeysSettings?.skillOverrides?.[context.skill]) {
        const override = apiKeysSettings.skillOverrides[context.skill][provider];
        if (override && override.source === 'custom' && override.key) {
            return override.key;
        }
    }
    // 2. Check runtime-level override
    if (context?.runtime && apiKeysSettings?.runtimeOverrides?.[context.runtime]) {
        const override = apiKeysSettings.runtimeOverrides[context.runtime][provider];
        if (override && override.source === 'custom' && override.key) {
            return override.key;
        }
    }
    // 3. Check global setting
    if (apiKeysSettings?.global?.[provider]) {
        return apiKeysSettings.global[provider];
    }
    // 4. Fall back to environment variable
    const envVarNames = API_KEY_ENV_VARS[provider];
    for (const envVar of envVarNames) {
        const value = process.env[envVar];
        if (value) {
            return value;
        }
    }
    return undefined;
}
/**
 * Create a masked copy of API keys settings for safe API responses
 *
 * @param apiKeys - The API keys settings to mask
 * @returns Masked copy safe for API responses
 */
export function maskApiKeysSettings(apiKeys) {
    const masked = { global: {}, runtimeOverrides: {}, skillOverrides: {} };
    // Mask global keys
    for (const provider of API_KEY_PROVIDERS) {
        if (apiKeys.global[provider]) {
            masked.global[provider] = maskApiKey(apiKeys.global[provider]);
        }
    }
    // Mask runtime overrides
    if (apiKeys.runtimeOverrides) {
        for (const [runtime, overrides] of Object.entries(apiKeys.runtimeOverrides)) {
            masked.runtimeOverrides[runtime] = {};
            for (const provider of API_KEY_PROVIDERS) {
                if (overrides[provider]) {
                    masked.runtimeOverrides[runtime][provider] = {
                        ...overrides[provider],
                        key: overrides[provider].key ? maskApiKey(overrides[provider].key) : '',
                    };
                }
            }
        }
    }
    // Mask skill overrides
    if (apiKeys.skillOverrides) {
        for (const [skill, overrides] of Object.entries(apiKeys.skillOverrides)) {
            masked.skillOverrides[skill] = {};
            for (const provider of API_KEY_PROVIDERS) {
                if (overrides[provider]) {
                    masked.skillOverrides[skill][provider] = {
                        ...overrides[provider],
                        key: overrides[provider].key ? maskApiKey(overrides[provider].key) : '',
                    };
                }
            }
        }
    }
    return masked;
}
/**
 * Get the display name for an AI runtime
 *
 * @param runtime - The AI runtime
 * @returns Human-readable display name
 */
export function getAIRuntimeDisplayName(runtime) {
    switch (runtime) {
        case 'claude-code':
            return 'Claude Code';
        case 'gemini-cli':
            return 'Gemini CLI';
        case 'codex-cli':
            return 'Codex CLI';
        case 'crewly-agent':
            return 'Crewly Agent';
        default:
            return runtime;
    }
}
//# sourceMappingURL=settings.types.js.map