"use strict";
/**
 * Config Routes - Unified config management and migration
 */
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 });
const express_1 = require("express");
const fs = __importStar(require("fs"));
const migration_manager_1 = require("../../config/migration-manager");
const unified_config_types_1 = require("../../config/unified-config-types");
const account_context_1 = require("../../auth/account-context");
const unified_config_types_2 = require("../../config/unified-config-types");
const auth_middleware_1 = require("../middleware/auth-middleware");
const sensitive_keys_1 = require("../../utils/sensitive-keys");
const config_loader_facade_1 = require("../../config/config-loader-facade");
const router = (0, express_1.Router)();
const LOCAL_CONFIG_ERROR = 'Local configuration endpoints require localhost access when dashboard auth is disabled.';
const REDACTED_SECRET_VALUE = '[redacted]';
const RAW_YAML_REDACTED_PATHS = new Set([
    'cliproxy.auth.api_key',
    'cliproxy.auth.management_secret',
    'cliproxy_server.remote.auth_token',
    'cliproxy_server.remote.management_key',
    'dashboard_auth.password_hash',
]);
router.use((req, res, next) => {
    if ((0, auth_middleware_1.requireLocalAccessWhenAuthDisabled)(req, res, LOCAL_CONFIG_ERROR)) {
        next();
    }
});
function redactSecretValue(value) {
    if (value === undefined) {
        return undefined;
    }
    return value.length > 0 ? REDACTED_SECRET_VALUE : value;
}
function sanitizeUnifiedConfigForDashboard(config) {
    return {
        ...config,
        global_env: config.global_env
            ? {
                ...config.global_env,
                env: Object.fromEntries(Object.entries(config.global_env.env).map(([key, value]) => [
                    key,
                    (0, sensitive_keys_1.isSensitiveKey)(key) && value ? REDACTED_SECRET_VALUE : value,
                ])),
            }
            : config.global_env,
        cliproxy: {
            ...config.cliproxy,
            auth: sanitizeCliproxyAuthConfig(config.cliproxy.auth),
            variants: Object.fromEntries(Object.entries(config.cliproxy.variants).map(([variantName, variantConfig]) => [
                variantName,
                sanitizeCliproxyVariantAuth(variantConfig),
            ])),
        },
        cliproxy_server: config.cliproxy_server
            ? {
                ...config.cliproxy_server,
                remote: {
                    ...config.cliproxy_server.remote,
                    auth_token: redactSecretValue(config.cliproxy_server.remote.auth_token) ?? '',
                    management_key: redactSecretValue(config.cliproxy_server.remote.management_key),
                },
            }
            : config.cliproxy_server,
        dashboard_auth: config.dashboard_auth
            ? {
                ...config.dashboard_auth,
                password_hash: redactSecretValue(config.dashboard_auth.password_hash) ?? '',
            }
            : config.dashboard_auth,
    };
}
function sanitizeCliproxyAuthConfig(auth) {
    if (!auth) {
        return auth;
    }
    return {
        ...auth,
        api_key: redactSecretValue(auth.api_key),
        management_secret: redactSecretValue(auth.management_secret),
    };
}
function sanitizeCliproxyVariantAuth(variantConfig) {
    return {
        ...variantConfig,
        auth: sanitizeCliproxyAuthConfig(variantConfig.auth),
    };
}
function normalizeYamlKey(rawKey) {
    const key = rawKey.trim();
    if ((key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))) {
        return key.slice(1, -1);
    }
    return key;
}
function splitYamlScalarAndComment(valuePortion) {
    let inSingleQuote = false;
    let inDoubleQuote = false;
    for (let index = 0; index < valuePortion.length; index += 1) {
        const char = valuePortion[index];
        if (char === "'" && !inDoubleQuote) {
            if (inSingleQuote && valuePortion[index + 1] === "'") {
                index += 1;
                continue;
            }
            inSingleQuote = !inSingleQuote;
            continue;
        }
        if (char === '"' && !inSingleQuote && valuePortion[index - 1] !== '\\') {
            inDoubleQuote = !inDoubleQuote;
            continue;
        }
        if (char === '#' &&
            !inSingleQuote &&
            !inDoubleQuote &&
            (index === 0 || /\s/.test(valuePortion[index - 1]))) {
            const prefix = valuePortion.slice(0, index);
            const spacing = prefix.match(/\s*$/)?.[0] ?? '';
            return {
                value: prefix.trimEnd(),
                comment: `${spacing}${valuePortion.slice(index)}`,
            };
        }
    }
    return {
        value: valuePortion.trimEnd(),
        comment: '',
    };
}
function redactYamlScalarLine(line) {
    const match = line.match(/^(\s*[^:#][^:]*:\s*)(.*)$/);
    if (!match) {
        return line;
    }
    const [, prefix, rawTail] = match;
    const leadingSpacing = rawTail.match(/^\s*/)?.[0] ?? '';
    const { value, comment } = splitYamlScalarAndComment(rawTail.slice(leadingSpacing.length));
    const trimmedValue = value.trim();
    if (trimmedValue.length === 0 || trimmedValue === '""' || trimmedValue === "''") {
        return line;
    }
    const quote = trimmedValue.startsWith('"') && trimmedValue.endsWith('"')
        ? '"'
        : trimmedValue.startsWith("'") && trimmedValue.endsWith("'")
            ? "'"
            : '';
    const redactedValue = quote ? `${quote}${REDACTED_SECRET_VALUE}${quote}` : REDACTED_SECRET_VALUE;
    return `${prefix}${leadingSpacing}${redactedValue}${comment}`;
}
function isYamlBlockScalarIndicator(value) {
    const trimmedValue = value.trim();
    if (!(trimmedValue.startsWith('|') || trimmedValue.startsWith('>'))) {
        return false;
    }
    const indicators = trimmedValue.slice(1);
    if (indicators.length === 0) {
        return true;
    }
    if (indicators.length > 2) {
        return false;
    }
    let sawChompingIndicator = false;
    let sawIndentIndicator = false;
    for (const indicator of indicators) {
        if ((indicator === '+' || indicator === '-') && !sawChompingIndicator) {
            sawChompingIndicator = true;
            continue;
        }
        if (/[1-9]/.test(indicator) && !sawIndentIndicator) {
            sawIndentIndicator = true;
            continue;
        }
        return false;
    }
    return true;
}
function shouldRedactRawYamlPath(pathKeys) {
    if (pathKeys.length === 3 &&
        pathKeys[0] === 'global_env' &&
        pathKeys[1] === 'env' &&
        (0, sensitive_keys_1.isSensitiveKey)(pathKeys[2])) {
        return true;
    }
    if (pathKeys.length === 3 &&
        pathKeys[0] === 'cliproxy' &&
        pathKeys[1] === 'auth' &&
        (pathKeys[2] === 'api_key' || pathKeys[2] === 'management_secret')) {
        return true;
    }
    if (pathKeys.length === 5 &&
        pathKeys[0] === 'cliproxy' &&
        pathKeys[1] === 'variants' &&
        pathKeys[3] === 'auth' &&
        (pathKeys[4] === 'api_key' || pathKeys[4] === 'management_secret')) {
        return true;
    }
    return RAW_YAML_REDACTED_PATHS.has(pathKeys.join('.'));
}
function redactRawConfigYamlForDashboard(content) {
    const stack = [];
    const redactedLines = [];
    const lines = content.split('\n');
    for (let index = 0; index < lines.length; index += 1) {
        const line = lines[index];
        const mappingMatch = line.match(/^(\s*)([^:#][^:]*):(.*)$/);
        if (!mappingMatch) {
            redactedLines.push(line);
            continue;
        }
        const [, indentText, rawKey, rawTail] = mappingMatch;
        if (rawKey.trimStart().startsWith('-')) {
            redactedLines.push(line);
            continue;
        }
        const indent = indentText.length;
        while (stack.length > 0 && stack[stack.length - 1].indent >= indent) {
            stack.pop();
        }
        stack.push({ indent, key: normalizeYamlKey(rawKey) });
        const currentPath = stack.map((entry) => entry.key);
        if (!shouldRedactRawYamlPath(currentPath)) {
            redactedLines.push(line);
            continue;
        }
        const redactedLine = redactYamlScalarLine(line);
        redactedLines.push(redactedLine);
        const leadingSpacing = rawTail.match(/^\s*/)?.[0] ?? '';
        const { value } = splitYamlScalarAndComment(rawTail.slice(leadingSpacing.length));
        const trimmedValue = value.trim();
        if (!isYamlBlockScalarIndicator(trimmedValue)) {
            continue;
        }
        while (index + 1 < lines.length) {
            const nextLine = lines[index + 1];
            if (nextLine.trim().length === 0) {
                index += 1;
                continue;
            }
            const nextIndent = nextLine.match(/^\s*/)?.[0].length ?? 0;
            if (nextIndent <= indent) {
                break;
            }
            index += 1;
        }
    }
    return redactedLines.join('\n');
}
function restoreRedactedSecretValue(currentValue, nextValue) {
    if (nextValue === undefined || nextValue === REDACTED_SECRET_VALUE) {
        return currentValue;
    }
    return nextValue;
}
function mergeGlobalEnvConfig(currentConfig, nextConfig) {
    const nextGlobalEnv = nextConfig.global_env;
    if (!nextGlobalEnv) {
        return currentConfig.global_env;
    }
    const currentEnv = currentConfig.global_env?.env ?? {};
    const nextEnv = nextGlobalEnv.env;
    return {
        enabled: nextGlobalEnv.enabled ?? currentConfig.global_env?.enabled ?? true,
        env: nextEnv === undefined
            ? currentEnv
            : Object.fromEntries(Object.entries(nextEnv).map(([key, value]) => {
                if (value === REDACTED_SECRET_VALUE && (0, sensitive_keys_1.isSensitiveKey)(key)) {
                    return [key, currentEnv[key] ?? value];
                }
                return [key, value];
            })),
    };
}
function mergeCliproxyServerConfig(currentConfig, nextConfig) {
    const nextServer = nextConfig.cliproxy_server;
    if (!nextServer) {
        return currentConfig.cliproxy_server;
    }
    const nextRemote = nextServer.remote;
    const currentServer = currentConfig.cliproxy_server ?? unified_config_types_2.DEFAULT_CLIPROXY_SERVER_CONFIG;
    return {
        remote: nextRemote === undefined
            ? currentServer.remote
            : {
                ...nextRemote,
                auth_token: restoreRedactedSecretValue(currentServer.remote.auth_token, nextRemote.auth_token) ??
                    '',
                management_key: restoreRedactedSecretValue(currentServer.remote.management_key, nextRemote.management_key),
            },
        fallback: nextServer.fallback ?? currentServer.fallback,
        local: nextServer.local ?? currentServer.local,
    };
}
function mergeDashboardAuthConfig(currentConfig, nextConfig) {
    const nextAuth = nextConfig.dashboard_auth;
    if (!nextAuth) {
        return currentConfig.dashboard_auth;
    }
    return {
        ...nextAuth,
        password_hash: restoreRedactedSecretValue(currentConfig.dashboard_auth?.password_hash, nextAuth.password_hash) ?? '',
    };
}
function mergeCliproxyAuthField(currentValue, nextAuth, key) {
    if (!Object.prototype.hasOwnProperty.call(nextAuth, key)) {
        return undefined;
    }
    return restoreRedactedSecretValue(currentValue, nextAuth[key]);
}
function mergeCliproxyAuthConfig(currentAuth, nextAuth) {
    if (!nextAuth) {
        return currentAuth;
    }
    const mergedAuth = { ...nextAuth };
    const apiKey = mergeCliproxyAuthField(currentAuth?.api_key, nextAuth, 'api_key');
    const managementSecret = mergeCliproxyAuthField(currentAuth?.management_secret, nextAuth, 'management_secret');
    if (apiKey === undefined) {
        delete mergedAuth.api_key;
    }
    else {
        mergedAuth.api_key = apiKey;
    }
    if (managementSecret === undefined) {
        delete mergedAuth.management_secret;
    }
    else {
        mergedAuth.management_secret = managementSecret;
    }
    return Object.keys(mergedAuth).length > 0 ? mergedAuth : undefined;
}
function mergeCliproxyVariantConfig(currentVariant, nextVariant) {
    const mergedVariant = { ...nextVariant };
    const mergedAuth = mergeCliproxyAuthConfig(currentVariant?.auth, nextVariant.auth);
    if (mergedAuth === undefined) {
        delete mergedVariant.auth;
    }
    else {
        mergedVariant.auth = mergedAuth;
    }
    return mergedVariant;
}
function mergeCliproxyConfig(currentConfig, nextConfig) {
    const nextCliproxy = nextConfig.cliproxy;
    if (!nextCliproxy) {
        return currentConfig.cliproxy;
    }
    const mergedCliproxy = {
        ...nextCliproxy,
        variants: nextCliproxy.variants === undefined
            ? currentConfig.cliproxy.variants
            : Object.fromEntries(Object.entries(nextCliproxy.variants).map(([variantName, nextVariant]) => [
                variantName,
                mergeCliproxyVariantConfig(currentConfig.cliproxy.variants[variantName], nextVariant),
            ])),
    };
    const mergedAuth = mergeCliproxyAuthConfig(currentConfig.cliproxy.auth, nextCliproxy.auth);
    if (mergedAuth === undefined) {
        delete mergedCliproxy.auth;
    }
    else {
        mergedCliproxy.auth = mergedAuth;
    }
    return mergedCliproxy;
}
function validateAndNormalizeAccountContextMetadata(config) {
    if (typeof config !== 'object' || config === null) {
        return 'Invalid config payload';
    }
    const candidate = config;
    const accounts = candidate.accounts;
    if (accounts === undefined) {
        return null;
    }
    if (typeof accounts !== 'object' || accounts === null || Array.isArray(accounts)) {
        return 'Invalid config.accounts: expected object';
    }
    for (const [accountName, accountValue] of Object.entries(accounts)) {
        if (typeof accountValue !== 'object' || accountValue === null || Array.isArray(accountValue)) {
            return `Invalid config.accounts.${accountName}: expected object`;
        }
        const account = accountValue;
        const mode = account.context_mode;
        const group = account.context_group;
        const continuity = account.continuity_mode;
        if (mode !== undefined && mode !== 'isolated' && mode !== 'shared') {
            return `Invalid config.accounts.${accountName}.context_mode: expected isolated|shared`;
        }
        if (group !== undefined && typeof group !== 'string') {
            return `Invalid config.accounts.${accountName}.context_group: expected string`;
        }
        if (continuity !== undefined && continuity !== 'standard' && continuity !== 'deeper') {
            return `Invalid config.accounts.${accountName}.continuity_mode: expected standard|deeper`;
        }
        if (mode !== 'shared' && group !== undefined) {
            return `Invalid config.accounts.${accountName}: context_group requires context_mode=shared`;
        }
        if (mode !== 'shared' && continuity !== undefined) {
            return `Invalid config.accounts.${accountName}: continuity_mode requires context_mode=shared`;
        }
        if (mode === 'shared' && typeof group === 'string' && group.trim().length > 0) {
            const normalizedGroup = (0, account_context_1.normalizeContextGroupName)(group);
            if (!(0, account_context_1.isValidContextGroupName)(normalizedGroup)) {
                return `Invalid config.accounts.${accountName}.context_group`;
            }
            account.context_group = normalizedGroup;
        }
        if (mode === 'shared') {
            account.continuity_mode =
                continuity === 'deeper' ? 'deeper' : account_context_1.DEFAULT_ACCOUNT_CONTINUITY_MODE;
        }
        if (mode === 'shared' && typeof group === 'string' && group.trim().length === 0) {
            return `Invalid config.accounts.${accountName}.context_group: shared mode requires a non-empty value`;
        }
        if (mode === 'isolated' && group !== undefined) {
            delete account.context_group;
        }
        if (mode === 'isolated' && continuity !== undefined) {
            delete account.continuity_mode;
        }
    }
    return null;
}
/**
 * GET /api/config/format - Return current config format and migration status
 */
router.get('/format', (_req, res) => {
    try {
        res.json({
            format: (0, config_loader_facade_1.getConfigFormat)(),
            migrationNeeded: (0, migration_manager_1.needsMigration)(),
            backups: (0, migration_manager_1.getBackupDirectories)(),
        });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * GET /api/config - Return unified config (excludes secrets)
 */
router.get('/', (_req, res) => {
    if (!(0, config_loader_facade_1.hasUnifiedConfig)()) {
        res.status(400).json({ error: 'Unified config not enabled' });
        return;
    }
    const config = (0, config_loader_facade_1.loadUnifiedConfig)();
    if (!config) {
        res.status(500).json({ error: 'Failed to load config' });
        return;
    }
    res.json(sanitizeUnifiedConfigForDashboard(config));
});
/**
 * GET /api/config/raw - Return raw YAML content for display
 */
router.get('/raw', (_req, res) => {
    const yamlPath = (0, config_loader_facade_1.getConfigYamlPath)();
    if (!fs.existsSync(yamlPath)) {
        res.status(404).json({ error: 'Config file not found' });
        return;
    }
    try {
        const content = fs.readFileSync(yamlPath, 'utf8');
        res.type('text/plain').send(redactRawConfigYamlForDashboard(content));
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
});
/**
 * PUT /api/config - Update unified config
 */
router.put('/', (req, res) => {
    const config = req.body;
    if (!(0, unified_config_types_1.isUnifiedConfig)(config)) {
        res.status(400).json({ error: 'Invalid config format' });
        return;
    }
    const accountContextError = validateAndNormalizeAccountContextMetadata(config);
    if (accountContextError) {
        res.status(400).json({ error: accountContextError });
        return;
    }
    try {
        (0, config_loader_facade_1.mutateConfig)((currentConfig) => {
            if ('setup_completed' in config) {
                currentConfig.setup_completed = config.setup_completed;
            }
            if ('default' in config) {
                currentConfig.default = config.default;
            }
            if (config.accounts !== undefined) {
                currentConfig.accounts = config.accounts;
            }
            if (config.profiles !== undefined) {
                currentConfig.profiles = config.profiles;
            }
            if (config.cliproxy !== undefined) {
                currentConfig.cliproxy = mergeCliproxyConfig(currentConfig, config);
            }
            if (config.proxy !== undefined) {
                currentConfig.proxy = config.proxy;
            }
            if (config.preferences !== undefined) {
                currentConfig.preferences = config.preferences;
            }
            if (config.websearch !== undefined) {
                currentConfig.websearch = config.websearch;
            }
            if (config.continuity !== undefined) {
                currentConfig.continuity = config.continuity;
            }
            if (config.copilot !== undefined) {
                currentConfig.copilot = config.copilot;
            }
            if (config.cursor !== undefined) {
                currentConfig.cursor = config.cursor;
            }
            if (config.quota_management !== undefined) {
                currentConfig.quota_management = config.quota_management;
            }
            if (config.thinking !== undefined) {
                currentConfig.thinking = config.thinking;
            }
            if (config.image_analysis !== undefined) {
                currentConfig.image_analysis = config.image_analysis;
            }
            if (config.global_env !== undefined) {
                currentConfig.global_env = mergeGlobalEnvConfig(currentConfig, config);
            }
            if (config.cliproxy_server !== undefined) {
                currentConfig.cliproxy_server = mergeCliproxyServerConfig(currentConfig, config);
            }
            if (config.dashboard_auth !== undefined) {
                currentConfig.dashboard_auth = mergeDashboardAuthConfig(currentConfig, config);
            }
        });
        res.json({ success: true });
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
});
/**
 * POST /api/config/migrate - Trigger migration from JSON to YAML
 */
router.post('/migrate', async (req, res) => {
    try {
        const dryRun = req.query.dryRun === 'true';
        if (!(0, migration_manager_1.needsMigration)()) {
            res.json({
                success: true,
                migratedFiles: [],
                warnings: [],
                alreadyMigrated: true,
            });
            return;
        }
        const result = await (0, migration_manager_1.migrate)(dryRun);
        res.json(result);
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * POST /api/config/rollback - Rollback migration to JSON format
 */
router.post('/rollback', async (req, res) => {
    try {
        const { backupPath } = req.body;
        if (!backupPath || typeof backupPath !== 'string') {
            res.status(400).json({ error: 'Missing required field: backupPath' });
            return;
        }
        const managedBackupPath = (0, migration_manager_1.resolveManagedBackupPath)(backupPath);
        if (!managedBackupPath) {
            res.status(400).json({
                error: 'Invalid backupPath. Must reference a managed CCS migration backup directory.',
            });
            return;
        }
        const success = await (0, migration_manager_1.rollback)(managedBackupPath);
        res.json({ success });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
exports.default = router;
//# sourceMappingURL=config-routes.js.map