"use strict";
/**
 * Misc Routes - Generic file API and global environment variables
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveThinkingProviderOverridesForSave = void 0;
const express_1 = require("express");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const helpers_1 = require("../../utils/helpers");
const cliproxy_1 = require("../../cliproxy");
const route_helpers_1 = require("./route-helpers");
const auth_middleware_1 = require("../middleware/auth-middleware");
const config_loader_facade_1 = require("../../config/config-loader-facade");
const router = (0, express_1.Router)();
router.use((req, res, next) => {
    if ((0, auth_middleware_1.requireLocalAccessWhenAuthDisabled)(req, res, 'Local configuration endpoints require localhost access when dashboard auth is disabled.')) {
        next();
    }
});
function resolveThinkingProviderOverridesForSave(currentProviderOverrides, updatesProviderOverrides, shouldClearProviderOverrides) {
    if (shouldClearProviderOverrides) {
        return undefined;
    }
    if (updatesProviderOverrides !== undefined) {
        return updatesProviderOverrides;
    }
    return currentProviderOverrides;
}
exports.resolveThinkingProviderOverridesForSave = resolveThinkingProviderOverridesForSave;
// ==================== Generic File API ====================
/**
 * GET /api/file - Read a file with path validation
 * Query params: path (required)
 * Returns: { content: string, mtime: number, readonly: boolean, path: string }
 */
router.get('/file', (req, res) => {
    const filePath = req.query.path;
    if (!filePath) {
        res.status(400).json({ error: 'Missing required query parameter: path' });
        return;
    }
    const validation = (0, route_helpers_1.validateFilePath)(filePath);
    if (!validation.valid) {
        res.status(403).json({ error: validation.error });
        return;
    }
    const expandedPath = (0, helpers_1.expandPath)(filePath);
    if (!fs.existsSync(expandedPath)) {
        res.status(404).json({ error: 'File not found' });
        return;
    }
    try {
        const stat = fs.statSync(expandedPath);
        const content = fs.readFileSync(expandedPath, 'utf8');
        res.json({
            content,
            mtime: stat.mtime.getTime(),
            readonly: validation.readonly,
            path: expandedPath,
        });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * PUT /api/file - Write a file with conflict detection and backup
 * Query params: path (required)
 * Body: { content: string, expectedMtime?: number }
 * Returns: { success: true, mtime: number, backupPath?: string }
 */
router.put('/file', (req, res) => {
    const filePath = req.query.path;
    const { content, expectedMtime } = req.body;
    if (!filePath) {
        res.status(400).json({ error: 'Missing required query parameter: path' });
        return;
    }
    if (typeof content !== 'string') {
        res.status(400).json({ error: 'Missing required field: content' });
        return;
    }
    const validation = (0, route_helpers_1.validateFilePath)(filePath);
    if (!validation.valid) {
        res.status(403).json({ error: validation.error });
        return;
    }
    if (validation.readonly) {
        res.status(403).json({ error: 'File is read-only' });
        return;
    }
    const expandedPath = (0, helpers_1.expandPath)(filePath);
    const ccsDir = (0, config_loader_facade_1.getCcsDir)();
    // Conflict detection (if file exists and expectedMtime provided)
    if (fs.existsSync(expandedPath) && expectedMtime !== undefined) {
        const stat = fs.statSync(expandedPath);
        if (stat.mtime.getTime() !== expectedMtime) {
            res.status(409).json({
                error: 'File modified externally',
                currentMtime: stat.mtime.getTime(),
            });
            return;
        }
    }
    try {
        // Create backup if file exists
        let backupPath;
        if (fs.existsSync(expandedPath)) {
            const backupDir = path.join(ccsDir, 'backups');
            if (!fs.existsSync(backupDir)) {
                fs.mkdirSync(backupDir, { recursive: true });
            }
            const filename = path.basename(expandedPath);
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            backupPath = path.join(backupDir, `${filename}.${timestamp}.bak`);
            fs.copyFileSync(expandedPath, backupPath);
        }
        // Ensure parent directory exists
        const parentDir = path.dirname(expandedPath);
        if (!fs.existsSync(parentDir)) {
            fs.mkdirSync(parentDir, { recursive: true });
        }
        // Write atomically
        const tempPath = expandedPath + '.tmp';
        fs.writeFileSync(tempPath, content);
        fs.renameSync(tempPath, expandedPath);
        const newStat = fs.statSync(expandedPath);
        res.json({
            success: true,
            mtime: newStat.mtime.getTime(),
            backupPath,
        });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * GET /api/files - List editable files in ~/.ccs/
 * Returns: { files: Array<{ name: string, path: string, mtime: number }> }
 */
router.get('/files', (_req, res) => {
    const ccsDir = (0, config_loader_facade_1.getCcsDir)();
    if (!fs.existsSync(ccsDir)) {
        res.json({ files: [] });
        return;
    }
    try {
        const entries = fs.readdirSync(ccsDir, { withFileTypes: true });
        const files = entries
            .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
            .map((entry) => {
            const filePath = path.join(ccsDir, entry.name);
            const stat = fs.statSync(filePath);
            return {
                name: entry.name,
                path: `~/.ccs/${entry.name}`,
                mtime: stat.mtime.getTime(),
            };
        })
            .sort((a, b) => a.name.localeCompare(b.name));
        res.json({ files });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
// ==================== Global Environment Variables ====================
/**
 * GET /api/global-env - Get global environment variables configuration
 * Returns the global_env section from config.yaml
 */
router.get('/global-env', (_req, res) => {
    try {
        const config = (0, config_loader_facade_1.getGlobalEnvConfig)();
        res.json(config);
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * PUT /api/global-env - Update global environment variables configuration
 * Updates the global_env section in config.yaml
 */
router.put('/global-env', (req, res) => {
    const { enabled, env } = req.body;
    // Validate env values before acquiring write lock
    if (env !== undefined && typeof env === 'object' && env !== null) {
        for (const [key, value] of Object.entries(env)) {
            if (typeof value !== 'string') {
                res.status(400).json({ error: `Invalid value for ${key}: must be a string` });
                return;
            }
        }
    }
    try {
        // Atomic read-modify-write — avoids race between load and save
        const updated = (0, config_loader_facade_1.mutateConfig)((config) => {
            config.global_env = {
                enabled: enabled ?? config.global_env?.enabled ?? true,
                env: env ?? config.global_env?.env ?? {},
            };
        });
        res.json({ success: true, config: updated.global_env });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
// ==================== Thinking Configuration ====================
/**
 * GET /api/thinking - Get thinking budget configuration
 * Returns the thinking section from config.yaml with mtime for optimistic locking
 */
router.get('/thinking', (_req, res) => {
    try {
        const config = (0, config_loader_facade_1.getThinkingConfig)();
        // W4: Include mtime for optimistic locking
        let lastModified;
        try {
            const stats = fs.statSync((0, config_loader_facade_1.getConfigYamlPath)());
            lastModified = stats.mtimeMs;
        }
        catch {
            // File may not exist yet
        }
        res.json({ config, lastModified });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * PUT /api/thinking - Update thinking budget configuration
 * Updates the thinking section in config.yaml
 * Supports optimistic locking via lastModified field
 */
router.put('/thinking', (req, res) => {
    try {
        const { lastModified, clear_override: clearOverrideFlag, clear_provider_overrides: clearProviderOverridesFlag, ...updates } = req.body;
        // W4: Optimistic locking - check if file was modified since last read
        if (lastModified !== undefined) {
            try {
                const stats = fs.statSync((0, config_loader_facade_1.getConfigYamlPath)());
                if (stats.mtimeMs > lastModified) {
                    res.status(409).json({
                        error: 'Config was modified by another process. Please refresh and try again.',
                        currentMtime: stats.mtimeMs,
                    });
                    return;
                }
            }
            catch {
                // File may not exist yet, allow creation
            }
        }
        const shouldClearOverride = clearOverrideFlag === true || updates.override === null;
        const shouldClearProviderOverrides = clearProviderOverridesFlag === true || updates.provider_overrides === null;
        let normalizedOverride;
        let normalizedProviderOverrides;
        // Validate mode if provided
        if (updates.mode !== undefined) {
            const validModes = ['auto', 'off', 'manual'];
            const normalizedMode = updates.mode.toLowerCase().trim();
            if (!validModes.includes(normalizedMode)) {
                res.status(400).json({ error: `Invalid mode: must be one of ${validModes.join(', ')}` });
                return;
            }
            updates.mode = normalizedMode;
        }
        // Validate override if provided (budget or level)
        if (updates.override !== undefined && updates.override !== null) {
            // C3: Reject objects/arrays - only number or string allowed
            if (typeof updates.override !== 'number' && typeof updates.override !== 'string') {
                res.status(400).json({
                    error: 'Invalid override: must be a number or string, not object/array',
                });
                return;
            }
            if (typeof updates.override === 'number') {
                if (!Number.isFinite(updates.override) ||
                    updates.override < cliproxy_1.THINKING_BUDGET_MIN ||
                    updates.override > cliproxy_1.THINKING_BUDGET_MAX) {
                    res.status(400).json({
                        error: `Invalid override: must be between ${cliproxy_1.THINKING_BUDGET_MIN} and ${cliproxy_1.THINKING_BUDGET_MAX}`,
                    });
                    return;
                }
                normalizedOverride = updates.override;
            }
            else if (typeof updates.override === 'string') {
                const normalizedValue = updates.override.toLowerCase().trim();
                const validValues = [...cliproxy_1.VALID_THINKING_LEVELS, ...cliproxy_1.THINKING_OFF_VALUES];
                if (!validValues.includes(normalizedValue)) {
                    res.status(400).json({
                        error: `Invalid override: must be a level name (${cliproxy_1.VALID_THINKING_LEVELS.join(', ')}) or a number`,
                    });
                    return;
                }
                normalizedOverride = normalizedValue === '0' ? 'off' : normalizedValue;
            }
        }
        // Validate tier_defaults if provided
        if (updates.tier_defaults !== undefined) {
            if (typeof updates.tier_defaults !== 'object' ||
                updates.tier_defaults === null ||
                Array.isArray(updates.tier_defaults)) {
                res.status(400).json({ error: 'Invalid tier_defaults: must be an object' });
                return;
            }
            const validLevels = [...cliproxy_1.VALID_THINKING_LEVELS];
            for (const [tier, level] of Object.entries(updates.tier_defaults)) {
                if (!cliproxy_1.VALID_THINKING_TIERS.includes(tier)) {
                    res.status(400).json({ error: `Invalid tier: ${tier}` });
                    return;
                }
                if (!validLevels.includes(level)) {
                    res.status(400).json({
                        error: `Invalid level for ${tier}: must be one of ${validLevels.join(', ')}`,
                    });
                    return;
                }
            }
        }
        // C4: Validate provider_overrides if provided (nested structure: Record<string, Partial<ThinkingTierDefaults>>)
        if (updates.provider_overrides !== undefined && updates.provider_overrides !== null) {
            if (typeof updates.provider_overrides !== 'object' ||
                updates.provider_overrides === null ||
                Array.isArray(updates.provider_overrides)) {
                res.status(400).json({ error: 'Invalid provider_overrides: must be an object' });
                return;
            }
            const validLevels = [...cliproxy_1.VALID_THINKING_LEVELS];
            const validTiers = [...cliproxy_1.VALID_THINKING_TIERS];
            const sanitizedOverrides = {};
            for (const [provider, tierOverrides] of Object.entries(updates.provider_overrides)) {
                if (typeof provider !== 'string' || provider.trim() === '') {
                    res
                        .status(400)
                        .json({ error: 'Invalid provider_overrides: keys must be non-empty strings' });
                    return;
                }
                // tierOverrides should be Partial<ThinkingTierDefaults>
                if (typeof tierOverrides !== 'object' || tierOverrides === null) {
                    res.status(400).json({
                        error: `Invalid provider_overrides for ${provider}: must be an object with tier→level mapping`,
                    });
                    return;
                }
                for (const [tier, level] of Object.entries(tierOverrides)) {
                    if (!validTiers.includes(tier)) {
                        res.status(400).json({
                            error: `Invalid tier '${tier}' in provider_overrides.${provider}: must be one of ${validTiers.join(', ')}`,
                        });
                        return;
                    }
                    if (typeof level !== 'string' || !validLevels.includes(level.toLowerCase().trim())) {
                        res.status(400).json({
                            error: `Invalid level for provider_overrides.${provider}.${tier}: must be one of ${validLevels.join(', ')}`,
                        });
                        return;
                    }
                    const normalizedProvider = provider.trim().toLowerCase();
                    const normalizedTier = tier.trim().toLowerCase();
                    const normalizedLevel = level.toLowerCase().trim();
                    sanitizedOverrides[normalizedProvider] = sanitizedOverrides[normalizedProvider] ?? {};
                    sanitizedOverrides[normalizedProvider][normalizedTier] = normalizedLevel;
                }
            }
            normalizedProviderOverrides =
                Object.keys(sanitizedOverrides).length > 0 ? sanitizedOverrides : undefined;
        }
        const config = (0, config_loader_facade_1.mutateConfig)((currentConfig) => {
            currentConfig.thinking = {
                mode: updates.mode ?? currentConfig.thinking?.mode ?? 'auto',
                override: shouldClearOverride
                    ? undefined
                    : updates.override !== undefined
                        ? normalizedOverride
                        : currentConfig.thinking?.override,
                tier_defaults: {
                    opus: updates.tier_defaults?.opus ?? currentConfig.thinking?.tier_defaults?.opus ?? 'high',
                    sonnet: updates.tier_defaults?.sonnet ??
                        currentConfig.thinking?.tier_defaults?.sonnet ??
                        'medium',
                    haiku: updates.tier_defaults?.haiku ?? currentConfig.thinking?.tier_defaults?.haiku ?? 'low',
                },
                provider_overrides: resolveThinkingProviderOverridesForSave(currentConfig.thinking?.provider_overrides, updates.provider_overrides !== undefined ? normalizedProviderOverrides : undefined, shouldClearProviderOverrides),
                show_warnings: updates.show_warnings ?? currentConfig.thinking?.show_warnings ?? true,
            };
        });
        // W4: Return new mtime for subsequent requests
        let newMtime;
        try {
            const stats = fs.statSync((0, config_loader_facade_1.getConfigYamlPath)());
            newMtime = stats.mtimeMs;
        }
        catch {
            // Ignore
        }
        res.json({ success: true, config: config.thinking, lastModified: newMtime });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
exports.default = router;
//# sourceMappingURL=misc-routes.js.map