/**
 * Configuration Service
 *
 * Centralized service for loading, validating, and managing Wave configuration files.
 * Replaces distributed configuration logic previously embedded in hook.ts.
 */
import { readFileSync, existsSync, promises as fs } from "fs";
import * as path from "path";
import { isValidHookEvent } from "../types/hooks.js";
import { logger } from "../utils/globalLogger.js";
import { getAllConfigPaths, getExistingConfigPaths, getUserConfigPaths, getProjectConfigPaths, } from "../utils/configPaths.js";
import { isValidEnvironmentVars, } from "../types/environment.js";
import { ConfigurationError, CONFIG_ERRORS, } from "../types/index.js";
import { DEFAULT_WAVE_MAX_INPUT_TOKENS, DEFAULT_WAVE_MAX_OUTPUT_TOKENS, } from "../utils/constants.js";
import { parseCustomHeaders } from "../utils/stringUtils.js";
/**
 * Default ConfigurationService implementation
 *
 * Provides centralized configuration loading, validation, and management.
 * Extracted from distributed logic in hook.ts with improved error handling.
 */
export class ConfigurationService {
    constructor() {
        this.currentConfiguration = null;
        this.options = {};
        this._configuredEnvKeys = new Set();
    }
    /**
     * Set agent options for configuration resolution
     */
    setOptions(options) {
        this.options = options;
    }
    // Core loading operations
    /**
     * Load and merge configuration with comprehensive validation
     */
    async loadMergedConfiguration(workdir) {
        try {
            // Use the merged configuration function (this loads user and project configs internally)
            const mergedConfig = loadMergedWaveConfig(workdir);
            if (!mergedConfig) {
                // Still set system environment variables even if no config is found
                const env = { WAVE_PROJECT_DIR: workdir };
                this.setEnvironmentVars(env);
                return {
                    configuration: { env },
                    success: true, // No config is valid
                    warnings: [
                        "No configuration files found in user or project directories",
                    ],
                };
            }
            // Comprehensive validation
            const validation = this.validateConfiguration(mergedConfig);
            if (!validation.isValid) {
                return {
                    configuration: null,
                    success: false,
                    error: `Merged configuration validation failed: ${validation.errors.join(", ")}`,
                    warnings: validation.warnings,
                };
            }
            // Success case
            this.currentConfiguration = mergedConfig;
            // Set environment variables from merged config and inject system variables
            const env = {
                ...(mergedConfig.env || {}),
                WAVE_PROJECT_DIR: workdir,
            };
            this.setEnvironmentVars(env);
            mergedConfig.env = env;
            return {
                configuration: mergedConfig,
                success: true,
                sourcePath: "merged configuration",
                warnings: validation.warnings,
            };
        }
        catch (error) {
            return {
                configuration: null,
                success: false,
                error: `Failed to load merged configuration from ${workdir}: ${error.message}`,
                warnings: [],
            };
        }
    }
    // Validation operations
    /**
     * Validate configuration object structure and values
     */
    validateConfiguration(config) {
        const result = {
            isValid: true,
            errors: [],
            warnings: [],
        };
        // Validate basic structure
        if (!config || typeof config !== "object") {
            result.isValid = false;
            result.errors.push("Configuration must be a valid object");
            return result;
        }
        // Validate hooks if present
        if (config.hooks !== undefined) {
            if (typeof config.hooks !== "object" || config.hooks === null) {
                result.isValid = false;
                result.errors.push("Hooks configuration must be an object");
            }
            else {
                for (const [event, eventConfigs] of Object.entries(config.hooks)) {
                    if (!isValidHookEvent(event)) {
                        result.warnings.push(`Unknown hook event: ${event}`);
                        continue;
                    }
                    if (!Array.isArray(eventConfigs)) {
                        result.isValid = false;
                        result.errors.push(`Hook event '${event}' must be an array`);
                        continue;
                    }
                    // Validate individual hook configurations
                    for (let i = 0; i < eventConfigs.length; i++) {
                        const hookConfig = eventConfigs[i];
                        if (!hookConfig || typeof hookConfig !== "object") {
                            result.isValid = false;
                            result.errors.push(`Hook configuration ${i} for event '${event}' must be an object`);
                        }
                    }
                }
            }
        }
        // Validate enabledPlugins if present
        if (config.enabledPlugins !== undefined) {
            if (typeof config.enabledPlugins !== "object" ||
                config.enabledPlugins === null) {
                result.isValid = false;
                result.errors.push("enabledPlugins configuration must be an object");
            }
            else {
                for (const [pluginId, enabled] of Object.entries(config.enabledPlugins)) {
                    if (typeof enabled !== "boolean") {
                        result.isValid = false;
                        result.errors.push(`Value for plugin '${pluginId}' in enabledPlugins must be a boolean`);
                    }
                    if (!pluginId.includes("@")) {
                        result.warnings.push(`Plugin ID '${pluginId}' in enabledPlugins should follow 'name@marketplace' format`);
                    }
                }
            }
        }
        // Validate environment variables if present
        if (config.env !== undefined) {
            const envValidation = validateEnvironmentConfig(config.env);
            if (!envValidation.isValid) {
                result.isValid = false;
                result.errors.push(...envValidation.errors);
            }
            result.warnings.push(...envValidation.warnings);
        }
        // Validate permissions if present
        if (config.permissions !== undefined) {
            if (typeof config.permissions !== "object" ||
                config.permissions === null) {
                result.isValid = false;
                result.errors.push("Permissions configuration must be an object");
            }
            else {
                // Validate allow if present
                if (config.permissions.allow !== undefined) {
                    if (!Array.isArray(config.permissions.allow)) {
                        result.isValid = false;
                        result.errors.push("Permissions allow must be an array of strings");
                    }
                    else if (!config.permissions.allow.every((rule) => typeof rule === "string")) {
                        result.isValid = false;
                        result.errors.push("All permission allow rules must be strings");
                    }
                }
                // Validate deny if present
                if (config.permissions.deny !== undefined) {
                    if (!Array.isArray(config.permissions.deny)) {
                        result.isValid = false;
                        result.errors.push("Permissions deny must be an array of strings");
                    }
                    else if (!config.permissions.deny.every((rule) => typeof rule === "string")) {
                        result.isValid = false;
                        result.errors.push("All permission deny rules must be strings");
                    }
                }
                // Validate permissionMode if present
                if (config.permissions.permissionMode !== undefined) {
                    const validModes = [
                        "default",
                        "bypassPermissions",
                        "acceptEdits",
                        "plan",
                        "dontAsk",
                    ];
                    if (!validModes.includes(config.permissions.permissionMode)) {
                        result.isValid = false;
                        result.errors.push(`Invalid permissionMode: "${config.permissions.permissionMode}". Must be one of: ${validModes.join(", ")}`);
                    }
                }
            }
        }
        // Validate autoMemoryEnabled if present
        if (config.autoMemoryEnabled !== undefined &&
            typeof config.autoMemoryEnabled !== "boolean") {
            result.isValid = false;
            result.errors.push("autoMemoryEnabled configuration must be a boolean");
        }
        // Validate autoMemoryFrequency if present
        if (config.autoMemoryFrequency !== undefined &&
            (typeof config.autoMemoryFrequency !== "number" ||
                config.autoMemoryFrequency <= 0)) {
            result.isValid = false;
            result.errors.push("autoMemoryFrequency configuration must be a positive number");
        }
        // Validate models if present
        if (config.models !== undefined) {
            if (typeof config.models !== "object" || config.models === null) {
                result.isValid = false;
                result.errors.push("models configuration must be an object");
            }
            else {
                for (const [modelName, modelConfig] of Object.entries(config.models)) {
                    if (typeof modelConfig !== "object" || modelConfig === null) {
                        result.isValid = false;
                        result.errors.push(`Configuration for model '${modelName}' must be an object`);
                    }
                }
            }
        }
        return result;
    }
    /**
     * Validate configuration file without loading
     */
    validateConfigurationFile(filePath) {
        const result = {
            isValid: true,
            errors: [],
            warnings: [],
        };
        if (!existsSync(filePath)) {
            result.isValid = false;
            result.errors.push(`Configuration file not found: ${filePath}`);
            return result;
        }
        try {
            const content = readFileSync(filePath, "utf-8");
            const config = JSON.parse(content);
            // Use the main validation method
            const configValidation = this.validateConfiguration(config);
            result.isValid = configValidation.isValid;
            result.errors = configValidation.errors;
            result.warnings = configValidation.warnings;
        }
        catch (error) {
            result.isValid = false;
            if (error instanceof SyntaxError) {
                result.errors.push(`Invalid JSON syntax in ${filePath}: ${error.message}`);
            }
            else {
                result.errors.push(`Error reading configuration file ${filePath}: ${error.message}`);
            }
        }
        return result;
    }
    // Utility operations
    /**
     * Set environment variables from configuration
     * This replaces direct process.env modification
     */
    setEnvironmentVars(env) {
        for (const [key, value] of Object.entries(env)) {
            if (process.env[key] !== undefined && !this._configuredEnvKeys.has(key)) {
                logger.warn(`Overriding environment variable: ${key}`);
            }
            process.env[key] = value;
            this._configuredEnvKeys.add(key);
        }
    }
    // =============================================================================
    // Configuration Resolution Methods (merged from configResolver.ts)
    // =============================================================================
    /**
     * Resolves gateway configuration from constructor args and environment
     * Resolution priority: options > env (from settings.json) > process.env > error
     * @param apiKey - API key override (optional)
     * @param baseURL - Base URL override (optional)
     * @param defaultHeaders - HTTP headers override (optional)
     * @param fetchOptions - Fetch options override (optional)
     * @param fetch - Custom fetch implementation override (optional)
     * @returns Resolved gateway configuration
     * @throws ConfigurationError if required configuration is missing after fallbacks
     */
    resolveGatewayConfig(apiKey, baseURL, defaultHeaders, fetchOptions, fetch) {
        // Resolve API key: override > options > env (settings.json) > process.env
        // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
        let resolvedApiKey;
        if (apiKey !== undefined) {
            resolvedApiKey = apiKey;
        }
        else if (this.options.apiKey !== undefined) {
            resolvedApiKey = this.options.apiKey;
        }
        else {
            resolvedApiKey = process.env.WAVE_API_KEY;
        }
        // Resolve base URL: override > options > env (settings.json) > process.env
        // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
        let resolvedBaseURL;
        if (baseURL !== undefined) {
            resolvedBaseURL = baseURL;
        }
        else if (this.options.baseURL !== undefined) {
            resolvedBaseURL = this.options.baseURL;
        }
        else {
            resolvedBaseURL = process.env.WAVE_BASE_URL || "";
        }
        // Fallback to process.env if still not resolved (for dynamic updates in tests)
        if (resolvedApiKey === undefined) {
            resolvedApiKey = process.env.WAVE_API_KEY;
        }
        if (!resolvedBaseURL) {
            resolvedBaseURL = process.env.WAVE_BASE_URL || "";
        }
        if (!resolvedBaseURL && baseURL === undefined) {
            throw new ConfigurationError(CONFIG_ERRORS.MISSING_BASE_URL, "baseURL", {
                constructor: baseURL,
                environment: process.env.WAVE_BASE_URL,
                settings: process.env.WAVE_BASE_URL,
            });
        }
        if (resolvedBaseURL.trim() === "") {
            throw new ConfigurationError(CONFIG_ERRORS.EMPTY_BASE_URL, "baseURL", resolvedBaseURL);
        }
        // Resolve custom headers from environment: env (settings.json) > process.env
        const envCustomHeaders = process.env.WAVE_CUSTOM_HEADERS || process.env.WAVE_CUSTOM_HEADERS || "";
        const parsedEnvHeaders = parseCustomHeaders(envCustomHeaders);
        // Merge headers: env headers < options < override
        const resolvedHeaders = {
            ...parsedEnvHeaders,
            ...this.options.defaultHeaders,
            ...defaultHeaders,
        };
        return {
            apiKey: resolvedApiKey,
            baseURL: resolvedBaseURL,
            defaultHeaders: Object.keys(resolvedHeaders).length > 0 ? resolvedHeaders : undefined,
            fetchOptions: fetchOptions ?? this.options.fetchOptions,
            fetch: fetch ?? this.options.fetch,
        };
    }
    /**
     * Resolves model configuration with fallbacks
     * Resolution priority: override > options > env (from settings.json) > process.env > default
     * @param model - Agent model override (optional)
     * @param fastModel - Fast model override (optional)
     * @param maxTokens - Max output tokens override (optional)
     * @param permissionMode - Permission mode override (optional)
     * @returns Resolved model configuration with defaults
     */
    resolveModelConfig(model, fastModel, maxTokens, permissionMode) {
        // Resolve agent model: override > options > env (settings.json) > process.env
        const resolvedAgentModel = model ||
            this.options.model ||
            process.env.WAVE_MODEL ||
            process.env.WAVE_MODEL;
        // Resolve fast model: override > options > env (settings.json) > process.env
        const resolvedFastModel = fastModel ||
            this.options.fastModel ||
            process.env.WAVE_FAST_MODEL ||
            process.env.WAVE_FAST_MODEL;
        // Validate required fields
        if (!resolvedAgentModel) {
            throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
                constructor: model,
                environment: process.env.WAVE_MODEL,
            });
        }
        if (!resolvedFastModel) {
            throw new ConfigurationError(CONFIG_ERRORS.MISSING_FAST_MODEL, "fastModel", {
                constructor: fastModel,
                environment: process.env.WAVE_FAST_MODEL,
            });
        }
        // Resolve max output tokens
        const resolvedMaxTokens = this.resolveMaxOutputTokens(maxTokens);
        const baseConfig = {
            model: resolvedAgentModel,
            fastModel: resolvedFastModel,
            maxTokens: resolvedMaxTokens,
            permissionMode: permissionMode ?? this.options.permissionMode,
        };
        // Merge model-specific settings from configuration
        const modelSpecificConfig = this.currentConfiguration?.models?.[resolvedAgentModel];
        if (modelSpecificConfig) {
            return {
                ...baseConfig,
                ...modelSpecificConfig,
            };
        }
        return baseConfig;
    }
    /**
     * Resolves token limit with fallbacks
     * Resolution priority: override > options > env (from settings.json) > process.env > default
     * @param constructorLimit - Token limit override (optional)
     * @returns Resolved token limit
     */
    resolveMaxInputTokens(constructorLimit) {
        // If override value provided, use it
        if (constructorLimit !== undefined) {
            return constructorLimit;
        }
        // If options value provided, use it
        if (this.options.maxInputTokens !== undefined) {
            return this.options.maxInputTokens;
        }
        // Try env (settings.json) first, then process.env
        const envMaxInputTokens = process.env.WAVE_MAX_INPUT_TOKENS || process.env.WAVE_MAX_INPUT_TOKENS;
        if (envMaxInputTokens) {
            const parsed = parseInt(envMaxInputTokens, 10);
            if (!isNaN(parsed)) {
                return parsed;
            }
        }
        // Use default
        return DEFAULT_WAVE_MAX_INPUT_TOKENS;
    }
    /**
     * Resolves preferred language with fallbacks
     * Resolution priority: override > options > settings.json > undefined
     * @param constructorLanguage - Language override (optional)
     * @returns Resolved language or undefined
     */
    resolveLanguage(constructorLanguage) {
        // 1. Override (highest priority)
        if (constructorLanguage !== undefined) {
            return constructorLanguage;
        }
        // 2. Agent options
        if (this.options.language !== undefined) {
            return this.options.language;
        }
        // 2. settings.json (merged)
        if (this.currentConfiguration?.language) {
            return this.currentConfiguration.language;
        }
        return undefined;
    }
    /**
     * Resolves auto-memory enabled state with fallbacks
     * Resolution priority: settings.json > WAVE_DISABLE_AUTO_MEMORY > default (true)
     * @returns Resolved auto-memory enabled state
     */
    resolveAutoMemoryEnabled() {
        // 1. settings.json (merged)
        if (this.currentConfiguration?.autoMemoryEnabled !== undefined) {
            return this.currentConfiguration.autoMemoryEnabled;
        }
        // 2. WAVE_DISABLE_AUTO_MEMORY environment variable
        const disableAutoMemory = process.env.WAVE_DISABLE_AUTO_MEMORY ||
            process.env.WAVE_DISABLE_AUTO_MEMORY;
        if (disableAutoMemory === "1" || disableAutoMemory === "true") {
            return false;
        }
        // 3. Default (true)
        return true;
    }
    /**
     * Resolves auto-memory extraction frequency with fallbacks
     * Resolution priority: settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
     * @returns Resolved auto-memory extraction frequency (turns)
     */
    resolveAutoMemoryFrequency() {
        // 1. settings.json (merged)
        if (this.currentConfiguration?.autoMemoryFrequency !== undefined) {
            return this.currentConfiguration.autoMemoryFrequency;
        }
        // 2. WAVE_AUTO_MEMORY_FREQUENCY environment variable
        const envFrequency = process.env.WAVE_AUTO_MEMORY_FREQUENCY ||
            process.env.WAVE_AUTO_MEMORY_FREQUENCY;
        if (envFrequency) {
            const parsed = parseInt(envFrequency, 10);
            if (!isNaN(parsed) && parsed > 0) {
                return parsed;
            }
        }
        // 3. Default (1)
        return 1;
    }
    /**
     * Resolves max output tokens with fallbacks
     * Resolution priority: override > options > env (from settings.json) > process.env > default
     * @param constructorLimit - Max output tokens override (optional)
     * @returns Resolved max output tokens
     */
    resolveMaxOutputTokens(constructorLimit) {
        // If override value provided, use it
        if (constructorLimit !== undefined) {
            return constructorLimit;
        }
        // If options value provided, use it
        if (this.options.maxTokens !== undefined) {
            return this.options.maxTokens;
        }
        // Try env (settings.json) first, then process.env
        const envMaxOutputTokens = process.env.WAVE_MAX_OUTPUT_TOKENS || process.env.WAVE_MAX_OUTPUT_TOKENS;
        if (envMaxOutputTokens) {
            const parsed = parseInt(envMaxOutputTokens, 10);
            if (!isNaN(parsed) && parsed > 0) {
                return parsed;
            }
        }
        // Use default
        return DEFAULT_WAVE_MAX_OUTPUT_TOKENS;
    }
    /**
     * Set the active model in the session
     */
    setModel(model) {
        this.options.model = model;
    }
    /**
     * Get all configured models from settings.json and environment
     */
    getConfiguredModels() {
        const models = new Set();
        // Add current model from options or environment
        const currentModel = this.options.model || process.env.WAVE_MODEL || process.env.WAVE_MODEL;
        if (currentModel) {
            models.add(currentModel);
        }
        // Add models from merged configuration
        if (this.currentConfiguration?.models) {
            Object.keys(this.currentConfiguration.models).forEach((model) => {
                models.add(model);
            });
        }
        return Array.from(models);
    }
    /**
     * Resolve all configuration file paths
     */
    getConfigurationPaths(workdir) {
        const allPaths = getAllConfigPaths(workdir);
        const existingPaths = getExistingConfigPaths(workdir);
        return {
            userPaths: allPaths.userPaths,
            projectPaths: allPaths.projectPaths,
            builtinPaths: allPaths.builtinPaths,
            allPaths: allPaths.allPaths,
            existingPaths: existingPaths.existingPaths,
        };
    }
    /**
     * Add a permission rule to the local settings.local.json
     */
    async addAllowedRule(workdir, rule) {
        const localConfigPath = path.join(workdir, ".wave", "settings.local.json");
        // Ensure .wave directory exists
        const waveDir = path.join(workdir, ".wave");
        if (!existsSync(waveDir)) {
            await fs.mkdir(waveDir, { recursive: true });
        }
        let config = {};
        if (existsSync(localConfigPath)) {
            try {
                const content = await fs.readFile(localConfigPath, "utf-8");
                config = JSON.parse(content);
            }
            catch {
                // If file is corrupted, start with empty config
            }
        }
        if (!config.permissions) {
            config.permissions = {};
        }
        if (!config.permissions.allow) {
            config.permissions.allow = [];
        }
        if (!config.permissions.allow.includes(rule)) {
            config.permissions.allow.push(rule);
            await fs.writeFile(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
        }
    }
    /**
     * Update the enabled state of a plugin in the specified scope
     */
    async updateEnabledPlugin(workdir, scope, pluginId, enabled) {
        if (scope !== "user" && !existsSync(workdir)) {
            throw new Error(`Working directory does not exist: ${workdir}`);
        }
        let configPath;
        if (scope === "user") {
            configPath = getUserConfigPaths()[0]; // settings.json
        }
        else if (scope === "project") {
            configPath = getProjectConfigPaths(workdir)[1]; // settings.json
        }
        else {
            configPath = getProjectConfigPaths(workdir)[0]; // local settings.local.json
        }
        // Ensure directory exists
        const configDir = path.dirname(configPath);
        if (!existsSync(configDir)) {
            await fs.mkdir(configDir, { recursive: true });
        }
        let config = {};
        if (existsSync(configPath)) {
            try {
                const content = await fs.readFile(configPath, "utf-8");
                config = JSON.parse(content);
            }
            catch {
                // Start with empty config if file is corrupted
            }
        }
        if (!config.enabledPlugins) {
            config.enabledPlugins = {};
        }
        config.enabledPlugins[pluginId] = enabled;
        await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
    }
    /**
     * Remove a plugin from the enabled plugins in the specified scope
     */
    async removeEnabledPlugin(workdir, scope, pluginId) {
        if (scope !== "user" && !existsSync(workdir)) {
            throw new Error(`Working directory does not exist: ${workdir}`);
        }
        let configPath;
        if (scope === "user") {
            configPath = getUserConfigPaths()[0]; // settings.json
        }
        else if (scope === "project") {
            configPath = getProjectConfigPaths(workdir)[1]; // settings.json
        }
        else {
            configPath = getProjectConfigPaths(workdir)[0]; // local settings.local.json
        }
        if (!existsSync(configPath)) {
            return; // Nothing to remove
        }
        try {
            const content = await fs.readFile(configPath, "utf-8");
            const config = JSON.parse(content);
            if (config.enabledPlugins && pluginId in config.enabledPlugins) {
                delete config.enabledPlugins[pluginId];
                await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
            }
        }
        catch {
            // Ignore errors for corrupted or non-existent files
        }
    }
    /**
     * Get merged enabled plugins from all scopes
     */
    getMergedEnabledPlugins(workdir) {
        const mergedConfig = loadMergedWaveConfig(workdir);
        return mergedConfig?.enabledPlugins || {};
    }
    /**
     * Load Wave configuration from a JSON file
     * Supports both hooks and environment variables with proper validation
     */
    loadWaveConfigFromFile(filePath) {
        return loadWaveConfigFromFile(filePath);
    }
}
// =============================================================================
// Extracted Configuration Functions
// =============================================================================
/**
 * Validate environment variable configuration
 */
export function validateEnvironmentConfig(env, configPath) {
    const result = {
        isValid: true,
        errors: [],
        warnings: [],
    };
    // Check if env is defined
    if (env === undefined || env === null) {
        return result; // undefined/null env is valid (means no env vars)
    }
    // Validate that env is a Record<string, string>
    if (!isValidEnvironmentVars(env)) {
        result.isValid = false;
        result.errors.push(`Invalid env field format${configPath ? ` in ${configPath}` : ""}. Environment variables must be a Record<string, string>.`);
        return result;
    }
    // Additional validation for environment variable names
    const envVars = env;
    for (const [key, value] of Object.entries(envVars)) {
        // Check for valid environment variable naming convention
        if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
            result.warnings.push(`Environment variable '${key}' does not follow standard naming convention (alphanumeric and underscores only).`);
        }
        // Check for empty values
        if (value === "") {
            result.warnings.push(`Environment variable '${key}' has an empty value.`);
        }
        // Check for reserved variable names that might cause conflicts
        const reservedNames = [
            "PATH",
            "HOME",
            "USER",
            "PWD",
            "SHELL",
            "TERM",
            "NODE_ENV",
        ];
        if (reservedNames.includes(key.toUpperCase())) {
            result.warnings.push(`Environment variable '${key}' overrides a system variable, which may cause unexpected behavior.`);
        }
    }
    return result;
}
/**
 * Merge environment configurations with project taking precedence over user
 */
export function mergeEnvironmentConfig(userEnv, projectEnv, options = {}) {
    const userVars = userEnv || {};
    const projectVars = projectEnv || {};
    const mergedVars = {};
    const conflicts = [];
    // Start with user environment variables
    Object.assign(mergedVars, userVars);
    // Override with project environment variables and track conflicts
    for (const [key, projectValue] of Object.entries(projectVars)) {
        const userValue = userVars[key];
        if (userValue !== undefined &&
            userValue !== projectValue &&
            options.includeConflictWarnings !== false) {
            // Conflict detected - project value takes precedence
            conflicts.push({
                key,
                userValue,
                projectValue,
                resolvedValue: projectValue,
            });
        }
        mergedVars[key] = projectValue;
    }
    return {
        userVars,
        projectVars,
        mergedVars,
        conflicts,
    };
}
/**
 * Load Wave configuration from a JSON file
 * Supports both hooks and environment variables with proper validation
 */
export function loadWaveConfigFromFile(filePath) {
    if (!existsSync(filePath)) {
        return null;
    }
    try {
        const content = readFileSync(filePath, "utf-8");
        const config = JSON.parse(content);
        return {
            hooks: config.hooks || undefined,
            env: config.env || undefined,
            permissions: config.permissions || undefined,
            enabledPlugins: config.enabledPlugins || undefined,
            language: config.language || undefined,
            autoMemoryEnabled: config.autoMemoryEnabled !== undefined
                ? config.autoMemoryEnabled
                : undefined,
            models: config.models || undefined,
        };
    }
    catch (error) {
        if (error instanceof SyntaxError) {
            throw new Error(`Invalid JSON syntax in ${filePath}: ${error.message}`);
        }
        // Re-throw validation errors and other errors as-is
        throw error;
    }
}
/**
 * Load and merge Wave configuration from both user and project sources
 * Project configuration takes precedence over user configuration
 * Checks .local.json files first, then falls back to .json files
 */
export function loadMergedWaveConfig(workdir) {
    const userPaths = getUserConfigPaths(); // [json]
    const projectPaths = getProjectConfigPaths(workdir); // [local, json]
    // Priority order (lowest to highest):
    // user settings.json -> project settings.json -> local settings.local.json
    const pathsToLoad = [
        userPaths[0], // user settings.json
        projectPaths[1], // project settings.json
        projectPaths[0], // local settings.local.json
    ];
    const configs = [];
    for (const path of pathsToLoad) {
        const config = loadWaveConfigFromFile(path);
        if (config) {
            configs.push(config);
        }
    }
    if (configs.length === 0) {
        return null;
    }
    // Merge all configurations in order
    const mergedConfig = {};
    for (const config of configs) {
        // Merge hooks
        if (config.hooks) {
            if (!mergedConfig.hooks)
                mergedConfig.hooks = {};
            for (const [event, eventConfigs] of Object.entries(config.hooks)) {
                if (!isValidHookEvent(event))
                    continue;
                if (!mergedConfig.hooks[event])
                    mergedConfig.hooks[event] = [];
                mergedConfig.hooks[event].push(...eventConfigs);
            }
        }
        // Merge env
        if (config.env) {
            const environmentContext = mergeEnvironmentConfig(mergedConfig.env, config.env, { includeConflictWarnings: true });
            mergedConfig.env = environmentContext.mergedVars;
        }
        // Merge permissions
        if (config.permissions) {
            if (!mergedConfig.permissions)
                mergedConfig.permissions = {};
            // Merge allow rules
            if (config.permissions.allow) {
                if (!mergedConfig.permissions.allow)
                    mergedConfig.permissions.allow = [];
                mergedConfig.permissions.allow = [
                    ...new Set([
                        ...mergedConfig.permissions.allow,
                        ...config.permissions.allow,
                    ]),
                ];
            }
            // Merge deny rules
            if (config.permissions.deny) {
                if (!mergedConfig.permissions.deny)
                    mergedConfig.permissions.deny = [];
                mergedConfig.permissions.deny = [
                    ...new Set([
                        ...mergedConfig.permissions.deny,
                        ...config.permissions.deny,
                    ]),
                ];
            }
            // Merge permissionMode (last one wins)
            if (config.permissions.permissionMode !== undefined) {
                mergedConfig.permissions.permissionMode =
                    config.permissions.permissionMode;
            }
            // Merge additionalDirectories
            if (config.permissions.additionalDirectories) {
                if (!mergedConfig.permissions.additionalDirectories)
                    mergedConfig.permissions.additionalDirectories = [];
                mergedConfig.permissions.additionalDirectories = [
                    ...new Set([
                        ...mergedConfig.permissions.additionalDirectories,
                        ...config.permissions.additionalDirectories,
                    ]),
                ];
            }
        }
        // Merge enabledPlugins
        if (config.enabledPlugins) {
            if (!mergedConfig.enabledPlugins)
                mergedConfig.enabledPlugins = {};
            Object.assign(mergedConfig.enabledPlugins, config.enabledPlugins);
        }
        // Merge language (last one wins)
        if (config.language !== undefined) {
            mergedConfig.language = config.language;
        }
        // Merge autoMemoryEnabled (last one wins)
        if (config.autoMemoryEnabled !== undefined) {
            mergedConfig.autoMemoryEnabled = config.autoMemoryEnabled;
        }
        // Merge autoMemoryFrequency (last one wins)
        if (config.autoMemoryFrequency !== undefined) {
            mergedConfig.autoMemoryFrequency = config.autoMemoryFrequency;
        }
        // Merge models
        if (config.models) {
            if (!mergedConfig.models)
                mergedConfig.models = {};
            for (const [modelName, modelConfig] of Object.entries(config.models)) {
                if (!mergedConfig.models[modelName]) {
                    mergedConfig.models[modelName] = {};
                }
                Object.assign(mergedConfig.models[modelName], modelConfig);
            }
        }
    }
    return {
        hooks: mergedConfig.hooks && Object.keys(mergedConfig.hooks).length > 0
            ? mergedConfig.hooks
            : undefined,
        env: mergedConfig.env && Object.keys(mergedConfig.env).length > 0
            ? mergedConfig.env
            : undefined,
        permissions: mergedConfig.permissions &&
            Object.keys(mergedConfig.permissions).length > 0
            ? mergedConfig.permissions
            : undefined,
        enabledPlugins: mergedConfig.enabledPlugins &&
            Object.keys(mergedConfig.enabledPlugins).length > 0
            ? mergedConfig.enabledPlugins
            : undefined,
        language: mergedConfig.language,
        autoMemoryEnabled: mergedConfig.autoMemoryEnabled,
        models: mergedConfig.models && Object.keys(mergedConfig.models).length > 0
            ? mergedConfig.models
            : undefined,
    };
}
