import { config as loadEnv } from 'dotenv';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { VALID_MODES } from '../app/types.js';
import { substituteEnvVars } from '../config/env-substitution.js';
import { loadAllMCPConfigs, loadAllProviderConfigs, } from '../config/mcp-config-loader.js';
import { getConfigPath } from '../config/paths.js';
import { getNotificationsPreference, loadPreferences, } from '../config/preferences.js';
import { defaultTheme, getThemeColors } from '../config/themes.js';
import { logError } from '../utils/message-queue.js';
import { DEFAULT_SINGLE_LINE_PASTE_THRESHOLD } from '../utils/paste-utils.js';
// Load .env file from working directory (shell environment takes precedence)
// Suppress dotenv console output by temporarily redirecting stdout
const envPath = join(process.cwd(), '.env');
if (existsSync(envPath)) {
    const originalWrite = process.stdout.write.bind(process.stdout);
    process.stdout.write = () => true;
    try {
        loadEnv({ path: envPath });
    }
    finally {
        process.stdout.write = originalWrite;
    }
}
// Hold a map of what config files are where
export const confDirMap = {};
// Find the closest config file for the requested configuration file
export function getClosestConfigFile(fileName) {
    try {
        const configDir = getConfigPath();
        // If NANOCODER_CONFIG_DIR is explicitly set, skip cwd and home checks
        // and use only the config directory (important for tests and explicit overrides)
        const isExplicitConfigDir = Boolean(process.env.NANOCODER_CONFIG_DIR);
        if (!isExplicitConfigDir) {
            // First, lets check for a working directory config
            const cwdPath = join(process.cwd(), fileName); // nosemgrep
            if (existsSync(cwdPath)) {
                // nosemgrep
                confDirMap[fileName] = cwdPath; // nosemgrep
                return cwdPath; // nosemgrep
            }
        }
        // Last, lets look for an user level config.
        // If the file doesn't exist, create it
        const configPath = join(configDir, fileName); // nosemgrep
        if (!existsSync(configPath)) {
            // nosemgrep
            createDefaultConfFile(configDir, fileName);
        }
        confDirMap[fileName] = configPath; // nosemgrep
        return configPath; // nosemgrep
    }
    catch (error) {
        logError(`Failed to load ${fileName}: ${String(error)}`);
    }
    // The code should never hit this, but it makes the TS compiler happy.
    return fileName;
}
function createDefaultConfFile(filePath, fileName) {
    try {
        // If we cant find any, lets assume this is the first user run, create the
        // correct file and direct the user to configure them correctly,
        const configFilePath = join(filePath, fileName); // nosemgrep
        if (!existsSync(configFilePath)) {
            // nosemgrep
            // Maybe add a better sample config?
            const sampleConfig = {};
            mkdirSync(filePath, { recursive: true });
            writeFileSync(configFilePath, // nosemgrep
            JSON.stringify(sampleConfig, null, 2), 'utf-8');
        }
    }
    catch (error) {
        logError(`Failed to write ${filePath}: ${String(error)}`);
    }
}
/**
 * Read a JSON config file and hand the parsed contents to `extract`. Returns
 * null when the file is missing, unparseable, or `extract` rejects it. The
 * `label` only flavours the error log.
 */
function tryLoadConfig(configPath, label, 
// biome-ignore lint/suspicious/noExplicitAny: parsed JSON is dynamically shaped
extract) {
    if (!existsSync(configPath)) {
        return null;
    }
    try {
        const config = JSON.parse(readFileSync(configPath, 'utf-8'));
        return extract(config);
    }
    catch (error) {
        logError(`Failed to load ${label} config from ${configPath}: ${String(error)}`);
    }
    return null;
}
/**
 * Resolve a config value with project-over-global precedence: try
 * `<cwd>/<fileName>` first, then `<configDir>/<fileName>`. Returns null when
 * neither file yields a value.
 */
function loadHierarchicalConfig(fileName, label, 
// biome-ignore lint/suspicious/noExplicitAny: parsed JSON is dynamically shaped
extract) {
    const projectResult = tryLoadConfig(join(process.cwd(), fileName), // nosemgrep
    label, extract);
    if (projectResult !== null) {
        return projectResult;
    }
    return tryLoadConfig(join(getConfigPath(), fileName), label, extract); // nosemgrep
}
// Load auto-compact configuration and Returns default config if not specified
function loadAutoCompactConfig() {
    const defaults = {
        enabled: true,
        threshold: 60,
        mode: 'conservative',
        strategy: 'llm',
        notifyUser: true,
    };
    return (loadHierarchicalConfig('agents.config.json', 'auto-compact', config => {
        const autoCompact = config.nanocoder?.autoCompact;
        if (autoCompact && typeof autoCompact === 'object') {
            return {
                enabled: autoCompact.enabled !== undefined
                    ? Boolean(autoCompact.enabled)
                    : defaults.enabled,
                threshold: validateThreshold(autoCompact.threshold ?? defaults.threshold),
                mode: validateMode(autoCompact.mode ?? defaults.mode),
                strategy: validateStrategy(autoCompact.strategy ?? defaults.strategy),
                notifyUser: autoCompact.notifyUser !== undefined
                    ? Boolean(autoCompact.notifyUser)
                    : defaults.notifyUser,
            };
        }
        return null;
    }) ?? defaults);
}
// Validate and clamp threshold to valid range (50-95)
function validateThreshold(threshold) {
    const num = typeof threshold === 'number' ? threshold : 60;
    return Math.max(50, Math.min(95, Math.round(num)));
}
// Validate compression mode
function validateMode(mode) {
    if (mode === 'default' || mode === 'aggressive' || mode === 'conservative') {
        return mode;
    }
    return 'conservative';
}
// Validate compression strategy
function validateStrategy(strategy) {
    if (strategy === 'llm' || strategy === 'mechanical') {
        return strategy;
    }
    return 'llm';
}
// Load session configuration and Returns default config if not specified
function loadSessionConfig() {
    const defaults = {
        autoSave: true,
        saveInterval: 30000, // 30 seconds
        maxSessions: 100,
        maxMessages: 1000,
        retentionDays: 30,
        directory: '',
    };
    const normalizeSessionNumber = (value, min, fallback) => {
        if (typeof value === 'number' && Number.isFinite(value)) {
            return Math.max(min, value);
        }
        return fallback;
    };
    return (loadHierarchicalConfig('nanocoder-preferences.json', 'session', config => {
        const sessions = config.nanocoder?.sessions;
        if (sessions && typeof sessions === 'object') {
            return {
                autoSave: sessions.autoSave !== undefined
                    ? Boolean(sessions.autoSave)
                    : defaults.autoSave,
                saveInterval: normalizeSessionNumber(sessions.saveInterval, 1000, // Minimum 1 second
                defaults.saveInterval ?? 30000),
                maxSessions: normalizeSessionNumber(sessions.maxSessions, 1, defaults.maxSessions ?? 100),
                maxMessages: normalizeSessionNumber(sessions.maxMessages, 1, defaults.maxMessages ?? 1000),
                retentionDays: normalizeSessionNumber(sessions.retentionDays, 1, defaults.retentionDays ?? 30),
                directory: sessions.directory || defaults.directory,
            };
        }
        return null;
    }) ?? defaults);
}
// Default ceiling on LLM turns for headless (--plain / ACP) conversations.
// High enough that legitimate long iterative jobs finish, low enough that a
// wedged model in CI can't run unbounded. Override via the NANOCODER_MAX_TURNS
// env var or `nanocoder.headless.maxTurns` in agents.config.json.
export const DEFAULT_HEADLESS_MAX_TURNS = 200;
// Load headless conversation limits. Env var wins (handy for CI), then
// agents.config.json, then the default.
function loadHeadlessConfig() {
    const defaults = {
        maxTurns: DEFAULT_HEADLESS_MAX_TURNS,
    };
    const envValue = process.env['NANOCODER_MAX_TURNS'];
    if (envValue !== undefined && envValue.trim() !== '') {
        const parsed = Number.parseInt(envValue, 10);
        if (Number.isFinite(parsed) && parsed >= 1) {
            return { maxTurns: parsed };
        }
    }
    return (loadHierarchicalConfig('agents.config.json', 'headless', config => {
        const headless = config.nanocoder?.headless;
        if (headless && typeof headless === 'object') {
            const value = headless.maxTurns;
            if (typeof value === 'number' && Number.isFinite(value)) {
                return { maxTurns: Math.max(1, Math.round(value)) };
            }
            return defaults;
        }
        return null;
    }) ?? defaults);
}
// Load paste configuration and Returns default config if not specified
function loadPasteConfig() {
    const defaults = {
        singleLineThreshold: DEFAULT_SINGLE_LINE_PASTE_THRESHOLD,
    };
    return (loadHierarchicalConfig('nanocoder-preferences.json', 'paste', config => {
        const paste = config.nanocoder?.paste;
        if (paste && typeof paste === 'object') {
            return {
                singleLineThreshold: typeof paste.singleLineThreshold === 'number' &&
                    Number.isFinite(paste.singleLineThreshold) &&
                    paste.singleLineThreshold > 0
                    ? Math.round(paste.singleLineThreshold)
                    : defaults.singleLineThreshold,
            };
        }
        return null;
    }) ?? defaults);
}
function loadNanocoderToolsConfig() {
    return (loadHierarchicalConfig('agents.config.json', 'nanocoderTools', config => {
        const nanocoderTools = config.nanocoder?.nanocoderTools;
        if (nanocoderTools && typeof nanocoderTools === 'object') {
            return substituteEnvVars(nanocoderTools);
        }
        return null;
    }) ?? undefined);
}
function loadAlwaysAllowConfig() {
    return (loadHierarchicalConfig('agents.config.json', 'alwaysAllow', config => {
        const alwaysAllow = config.nanocoder?.alwaysAllow;
        if (Array.isArray(alwaysAllow)) {
            return alwaysAllow.filter((item) => typeof item === 'string');
        }
        return null;
    }) ?? undefined);
}
function loadDisabledToolsConfig() {
    return (loadHierarchicalConfig('agents.config.json', 'disabledTools', config => {
        const disabledTools = config.nanocoder?.disabledTools;
        if (Array.isArray(disabledTools)) {
            return disabledTools.filter((item) => typeof item === 'string');
        }
        return null;
    }) ?? undefined);
}
function loadSystemPromptConfig() {
    return (loadHierarchicalConfig('agents.config.json', 'systemPrompt', config => {
        const systemPrompt = config.nanocoder?.systemPrompt;
        if (!systemPrompt || typeof systemPrompt !== 'object') {
            return null;
        }
        const result = {};
        if (systemPrompt.mode === 'replace' || systemPrompt.mode === 'append') {
            result.mode = systemPrompt.mode;
        }
        if (typeof systemPrompt.content === 'string') {
            result.content = systemPrompt.content;
        }
        if (typeof systemPrompt.file === 'string') {
            result.file = systemPrompt.file;
        }
        if (result.content === undefined && result.file === undefined) {
            return null;
        }
        return result;
    }) ?? undefined);
}
// Load notifications configuration from preferences
function loadNotificationsConfig() {
    return getNotificationsPreference();
}
export function loadDefaultMode() {
    return (loadHierarchicalConfig('agents.config.json', 'defaultMode', config => {
        const defaultMode = config.nanocoder?.defaultMode;
        if (typeof defaultMode === 'string') {
            const normalized = defaultMode.toLowerCase().trim();
            if (VALID_MODES.includes(normalized)) {
                return normalized;
            }
        }
        return null;
    }) ?? undefined);
}
// Function to load app configuration from agents.config.json if it exists
function loadAppConfig() {
    // Load providers from the new hierarchical configuration system
    const providers = loadAllProviderConfigs();
    // Load MCP servers from the new hierarchical configuration system
    const mcpServersWithSource = loadAllMCPConfigs();
    const mcpServers = mcpServersWithSource.map(item => item.server);
    // Load auto-compact configuration
    const autoCompact = loadAutoCompactConfig();
    // Load session configuration
    const sessions = loadSessionConfig();
    // Load headless conversation limits
    const headless = loadHeadlessConfig();
    // Load paste configuration
    const paste = loadPasteConfig();
    // Load nanocoder tools configuration
    const nanocoderTools = loadNanocoderToolsConfig();
    // Load top-level alwaysAllow (for non-interactive mode and as fallback)
    const alwaysAllow = loadAlwaysAllowConfig();
    // Load top-level disabledTools (filtered out of every tool-availability path)
    const disabledTools = loadDisabledToolsConfig();
    // Load custom system prompt override
    const systemPrompt = loadSystemPromptConfig();
    // Load notifications configuration
    const notifications = loadNotificationsConfig();
    return {
        providers,
        mcpServers,
        autoCompact,
        sessions,
        headless,
        paste,
        nanocoderTools,
        alwaysAllow,
        disabledTools,
        systemPrompt,
        notifications,
    };
}
let _appConfig = null;
/**
 * Lazy-loaded app config to avoid circular dependencies during module initialization
 * @public
 */
export function getAppConfig() {
    if (!_appConfig) {
        _appConfig = loadAppConfig();
    }
    return _appConfig;
}
// Function to reload the app configuration (useful after config file changes)
export function reloadAppConfig() {
    _appConfig = loadAppConfig();
}
// Function to clear the cached app configuration (useful for testing)
export function clearAppConfig() {
    _appConfig = null;
}
let cachedColors = null;
export function getColors() {
    if (!cachedColors) {
        const preferences = loadPreferences();
        const selectedTheme = preferences.selectedTheme || defaultTheme;
        cachedColors = getThemeColors(selectedTheme);
    }
    return cachedColors;
}
//# sourceMappingURL=index.js.map