"use strict";
/**
 * Core config generation for CLIProxyAPI
 * Handles unified config.yaml generation for all providers
 */
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.deleteConfig = exports.deleteConfigForPort = exports.configExists = exports.configNeedsRegeneration = exports.regenerateConfig = exports.parseUserApiKeys = exports.generateConfig = exports.getManagementPanelRepository = exports.getProviderConfig = exports.PLUS_MANAGEMENT_PANEL_REPOSITORY = exports.ORIGINAL_MANAGEMENT_PANEL_REPOSITORY = exports.CLIPROXY_CONFIG_VERSION = exports.CCS_CONTROL_PANEL_SECRET = exports.CCS_INTERNAL_API_KEY = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const provider_capabilities_1 = require("../provider-capabilities");
const base_config_loader_1 = require("../config/base-config-loader");
const types_1 = require("../ai-providers/types");
const auth_token_manager_1 = require("../auth/auth-token-manager");
const model_id_normalizer_1 = require("../ai-providers/model-id-normalizer");
const path_resolver_1 = require("./path-resolver");
const port_manager_1 = require("./port-manager");
const config_loader_facade_1 = require("../../config/config-loader-facade");
const docker_key_rotation_1 = require("../../docker/docker-key-rotation");
/** Internal API key for CCS-managed requests */
exports.CCS_INTERNAL_API_KEY = 'ccs-internal-managed';
/** Simple secret key for Control Panel login (user-facing) */
exports.CCS_CONTROL_PANEL_SECRET = 'ccs';
/**
 * Config version - bump when config format changes to trigger regeneration
 * v1: Initial config (port, auth-dir, api-keys only)
 * v2: Full-featured config with dashboard, quota mgmt, simplified key
 * v3: Logging disabled by default (user opt-in via ~/.ccs/config.yaml)
 * v4: Added Kiro (AWS) and GitHub Copilot providers
 * v5: Added disable-cooling: true for stability
 * v6: Added oauth-model-alias with Opus 4.6 support
 * v7: Added fork:true for Claude model aliases (keep both upstream and alias names)
 * v8: Added Gemini 3.1 preview aliases for provider routing compatibility
 * v9: Added resilient alias compatibility expansion and cache-assisted alias enrichment
 * v10: Migrated deprecated gemini-claude-* aliases to upstream claude-* aliases
 * v11: Migrated deprecated claude-sonnet-4-6-thinking aliases to claude-sonnet-4-6
 * v12: Removed denylisted Antigravity Claude 4.5 aliases
 * v13: Removed aggressive Gemini alias expansion to reduce model list noise in Control Panel
 * v14: Added Gemini 3.1 Flash Antigravity aliases for upcoming rollout compatibility
 * v15: Prune stale generated Antigravity Gemini preview aliases during regeneration
 * v16: Narrow stale Gemini alias cleanup to broad multi-version guessed ranges
 * v17: Persist routing.strategy from CCS unified config
 * v18: Persist routing.session-affinity and routing.session-affinity-ttl from CCS unified config
 * v19: Persist backend-aware management panel repository from CCS unified config
 */
exports.CLIPROXY_CONFIG_VERSION = 19;
exports.ORIGINAL_MANAGEMENT_PANEL_REPOSITORY = 'https://github.com/router-for-me/Cli-Proxy-API-Management-Center';
exports.PLUS_MANAGEMENT_PANEL_REPOSITORY = 'https://github.com/kaitranntt/Cli-Proxy-API-Management-Center';
const DEPRECATED_ANTIGRAVITY_ALIAS_PREFIX = 'gemini-claude-';
const UPSTREAM_CLAUDE_ALIAS_PREFIX = 'claude-';
const DEPRECATED_ANTIGRAVITY_SONNET_46_THINKING_REGEX = /^claude-sonnet-4(?:[.-])6-thinking$/i;
const CANONICAL_ANTIGRAVITY_SONNET_46_ALIAS = 'claude-sonnet-4-6';
/**
 * Default Antigravity oauth-model-alias entries.
 * Maps user-facing model names to Antigravity internal model names.
 * Additional compatibility aliases are derived automatically at generation time.
 */
const DEFAULT_ANTIGRAVITY_ALIASES = [
    { name: 'rev19-uic3-1p', alias: 'gemini-2.5-computer-use-preview-10-2025' },
    { name: 'gemini-3-pro-image', alias: 'gemini-3-pro-image-preview' },
    { name: 'gemini-3-pro-high', alias: 'gemini-3-pro-preview' },
    { name: 'gemini-3-pro-high', alias: 'gemini-3.1-pro-preview' },
    { name: 'gemini-3-pro-high', alias: 'gemini-3.1-pro-preview-customtools' },
    { name: 'gemini-3-flash', alias: 'gemini-3-flash-preview' },
    { name: 'gemini-3-flash', alias: 'gemini-3.1-flash-preview' },
    { name: 'claude-sonnet-4-6', alias: 'claude-sonnet-4-6', fork: true },
    // Backward compatibility: legacy sonnet thinking alias now routes to canonical model ID.
    { name: 'claude-sonnet-4-6-thinking', alias: 'claude-sonnet-4-6', fork: true },
    { name: 'claude-opus-4-6-thinking', alias: 'claude-opus-4-6-thinking', fork: true },
];
const BUILT_IN_GEMINI_ALIAS_NAMES = new Set(DEFAULT_ANTIGRAVITY_ALIASES.filter((entry) => entry.alias.startsWith('gemini-3')).map((entry) => entry.name));
const MIN_STALE_GUESSED_GEMINI_MINOR_VERSIONS = 2;
const MIN_STALE_HIGH_ONLY_GEMINI_MINOR_VERSIONS = 3;
const MIN_STALE_GUESSED_GEMINI_AVERAGE_VARIANTS_PER_MINOR = 2;
const LEGACY_GEMINI_STALE_ALIAS_MIGRATION_VERSION = 16;
const MAX_LEGACY_MANUAL_GEMINI_MINOR_VERSION = 2;
const GO_DURATION_SEGMENT = String.raw `(?:\d+(?:\.\d+)?(?:ns|us|µs|μs|ms|s|m|h))`;
const GO_DURATION_PATTERN = new RegExp(`^${GO_DURATION_SEGMENT}+$`);
/**
 * Get provider configuration
 * Model mappings are loaded from config/base-{provider}.settings.json
 */
function getProviderConfig(provider) {
    // Load models from base config file
    const models = (0, base_config_loader_1.getModelMappingFromConfig)(provider);
    return {
        name: provider,
        displayName: (0, provider_capabilities_1.getProviderDisplayName)(provider),
        models,
        requiresOAuth: true, // All CLIProxy providers require OAuth
    };
}
exports.getProviderConfig = getProviderConfig;
/**
 * Get CLIProxy logging settings from user config.
 * Defaults to disabled to prevent disk bloat.
 */
function getLoggingSettings() {
    const config = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)();
    return {
        loggingToFile: config.cliproxy.logging?.enabled ?? false,
        requestLog: config.cliproxy.logging?.request_log ?? false,
    };
}
function getRoutingStrategy() {
    const config = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)();
    return config.cliproxy?.routing?.strategy === 'fill-first' ? 'fill-first' : 'round-robin';
}
function getSessionAffinityEnabled() {
    return (0, config_loader_facade_1.loadOrCreateUnifiedConfig)().cliproxy?.routing?.session_affinity ?? false;
}
function getSessionAffinityTtl() {
    const ttl = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)().cliproxy?.routing?.session_affinity_ttl?.trim();
    return ttl && GO_DURATION_PATTERN.test(ttl) && hasPositiveDuration(ttl) ? ttl : '1h';
}
function normalizeManagementPanelRepository(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
function getDefaultManagementPanelRepository(backend) {
    return backend === 'plus'
        ? exports.PLUS_MANAGEMENT_PANEL_REPOSITORY
        : exports.ORIGINAL_MANAGEMENT_PANEL_REPOSITORY;
}
function getManagementPanelRepository() {
    const config = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)();
    return (normalizeManagementPanelRepository(config.cliproxy?.management_panel_repository) ??
        getDefaultManagementPanelRepository(config.cliproxy?.backend));
}
exports.getManagementPanelRepository = getManagementPanelRepository;
function quoteYamlString(value) {
    return JSON.stringify(value);
}
function hasPositiveDuration(value) {
    const segments = value.match(new RegExp(GO_DURATION_SEGMENT, 'g'));
    if (!segments) {
        return false;
    }
    return segments.some((segment) => {
        const numeric = parseFloat(segment);
        return Number.isFinite(numeric) && numeric > 0;
    });
}
function sanitizeYamlScalar(rawValue) {
    const trimmed = rawValue.trim();
    if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
        (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
        return trimmed.slice(1, -1).trim();
    }
    return trimmed;
}
function parseManagementPanelRepository(content) {
    const match = content.match(/^\s*panel-github-repository:\s*(.+?)\s*$/m);
    return match ? sanitizeYamlScalar(match[1]) : null;
}
function normalizeAntigravityAlias(rawAlias) {
    const normalized = sanitizeYamlScalar(rawAlias);
    if (normalized.toLowerCase().startsWith(DEPRECATED_ANTIGRAVITY_ALIAS_PREFIX)) {
        const migratedPrefix = UPSTREAM_CLAUDE_ALIAS_PREFIX + normalized.slice(DEPRECATED_ANTIGRAVITY_ALIAS_PREFIX.length);
        return normalizeAntigravityAlias(migratedPrefix);
    }
    if (DEPRECATED_ANTIGRAVITY_SONNET_46_THINKING_REGEX.test(normalized)) {
        return CANONICAL_ANTIGRAVITY_SONNET_46_ALIAS;
    }
    return normalized;
}
function addAliasEntry(entries, indexByKey, entry) {
    const normalized = {
        name: sanitizeYamlScalar(entry.name),
        alias: normalizeAntigravityAlias(entry.alias),
        fork: entry.fork || undefined,
    };
    if (!normalized.name || !normalized.alias)
        return;
    if ((0, model_id_normalizer_1.getDeniedModelIdReasonForProvider)(normalized.name, 'agy'))
        return;
    if ((0, model_id_normalizer_1.getDeniedModelIdReasonForProvider)(normalized.alias, 'agy'))
        return;
    const key = `${normalized.name}\u0000${normalized.alias}`;
    const existingIndex = indexByKey.get(key);
    if (existingIndex !== undefined) {
        if (normalized.fork)
            entries[existingIndex].fork = true;
        return;
    }
    indexByKey.set(key, entries.length);
    entries.push(normalized);
}
function buildAntigravityAliasKey(entry) {
    return `${sanitizeYamlScalar(entry.name)}\u0000${normalizeAntigravityAlias(entry.alias)}`;
}
function parseExistingAntigravityAliases(existingAliases) {
    const entries = [];
    const lines = existingAliases.replace(/\r\n/g, '\n').split('\n');
    let currentChannel = '';
    let currentName = '';
    let currentAlias = '';
    let currentFork = false;
    const flushCurrent = () => {
        if (currentName && currentAlias && (!currentChannel || currentChannel === 'antigravity')) {
            entries.push({
                name: sanitizeYamlScalar(currentName),
                alias: sanitizeYamlScalar(currentAlias),
                fork: currentFork || undefined,
            });
        }
        currentName = '';
        currentAlias = '';
        currentFork = false;
    };
    for (const line of lines) {
        const channelMatch = line.match(/^\s{2}([a-zA-Z0-9_-]+):\s*$/);
        if (channelMatch) {
            flushCurrent();
            currentChannel = channelMatch[1].trim().toLowerCase();
            continue;
        }
        if (currentChannel && currentChannel !== 'antigravity')
            continue;
        const nameMatch = line.match(/^\s+-\s*name:\s*(.+)/);
        const aliasMatch = line.match(/^\s+alias:\s*(.+)/);
        const forkMatch = line.match(/^\s+fork:\s*(.+)/);
        if (nameMatch) {
            flushCurrent();
            currentName = nameMatch[1];
            continue;
        }
        if (aliasMatch) {
            currentAlias = aliasMatch[1];
            continue;
        }
        if (forkMatch) {
            currentFork = sanitizeYamlScalar(forkMatch[1]).toLowerCase() === 'true';
        }
    }
    flushCurrent();
    return entries;
}
function getConfigVersionFromContent(content) {
    const versionMatch = content.match(/CCS v(\d+)/);
    if (!versionMatch) {
        return null;
    }
    const parsedVersion = Number.parseInt(versionMatch[1], 10);
    return Number.isNaN(parsedVersion) ? null : parsedVersion;
}
function toDottedGeminiVersionAlias(alias) {
    const match = alias.match(/^(gemini-\d+)-(\d+)(-.+)$/);
    if (!match)
        return null;
    return `${match[1]}.${match[2]}${match[3]}`;
}
function toHyphenatedGeminiVersionAlias(alias) {
    const match = alias.match(/^(gemini-\d+)\.(\d+)(-.+)$/);
    if (!match)
        return null;
    return `${match[1]}-${match[2]}${match[3]}`;
}
function buildGeminiCompatibilityAliases(alias) {
    if (!alias.startsWith('gemini-') || !alias.includes('-preview'))
        return [];
    const variants = new Set();
    const queue = [alias];
    const enqueue = (candidate) => {
        if (!candidate || candidate === alias || variants.has(candidate))
            return;
        variants.add(candidate);
        queue.push(candidate);
    };
    const visited = new Set();
    while (queue.length > 0) {
        const current = queue.pop();
        if (!current || visited.has(current))
            continue;
        visited.add(current);
        if (current.startsWith('gemini-') && current.includes('-preview')) {
            if (current.endsWith('-customtools')) {
                enqueue(current.slice(0, -'-customtools'.length));
            }
            else {
                enqueue(`${current}-customtools`);
            }
        }
        const dotted = toDottedGeminiVersionAlias(current);
        if (dotted)
            enqueue(dotted);
        const hyphenated = toHyphenatedGeminiVersionAlias(current);
        if (hyphenated)
            enqueue(hyphenated);
    }
    return [...variants];
}
function getCompatibilityAliases(entries) {
    const compatibilityAliases = [];
    for (const entry of entries) {
        const variants = buildGeminiCompatibilityAliases(entry.alias);
        for (const variant of variants) {
            compatibilityAliases.push({
                name: entry.name,
                alias: variant,
                fork: entry.fork,
            });
        }
    }
    return compatibilityAliases;
}
function buildGeneratedAntigravityAliases() {
    const generatedAliases = [];
    const generatedAliasIndex = new Map();
    for (const alias of DEFAULT_ANTIGRAVITY_ALIASES) {
        addAliasEntry(generatedAliases, generatedAliasIndex, alias);
    }
    for (const alias of getCompatibilityAliases(generatedAliases)) {
        addAliasEntry(generatedAliases, generatedAliasIndex, alias);
    }
    return generatedAliases;
}
const GENERATED_ANTIGRAVITY_ALIASES = buildGeneratedAntigravityAliases();
const GENERATED_ANTIGRAVITY_ALIAS_MAP = new Map(GENERATED_ANTIGRAVITY_ALIASES.map((entry) => [buildAntigravityAliasKey(entry), entry]));
function serializeAntigravityAliases(entries) {
    if (entries.length === 0) {
        return '';
    }
    const lines = ['  antigravity:'];
    for (const entry of entries) {
        lines.push(`    - name: ${entry.name}`);
        lines.push(`      alias: ${entry.alias}`);
        if (entry.fork) {
            lines.push('      fork: true');
        }
    }
    return lines.join('\n');
}
function getLegacyGeneratedGeminiPreviewInfo(entry) {
    if (!BUILT_IN_GEMINI_ALIAS_NAMES.has(entry.name)) {
        return null;
    }
    const normalizedAlias = normalizeAntigravityAlias(entry.alias).toLowerCase();
    if (!normalizedAlias.startsWith('gemini-3') || !normalizedAlias.includes('-preview')) {
        return null;
    }
    const aliasWithoutCustomtools = normalizedAlias.endsWith('-customtools')
        ? normalizedAlias.slice(0, -'-customtools'.length)
        : normalizedAlias;
    const canonicalAlias = toDottedGeminiVersionAlias(aliasWithoutCustomtools) ?? aliasWithoutCustomtools;
    const minorVersionMatch = canonicalAlias.match(/^gemini-3\.(\d+)(-.+-preview)$/);
    if (!minorVersionMatch) {
        return null;
    }
    return {
        nameKey: sanitizeYamlScalar(entry.name),
        minorVersion: minorVersionMatch[1],
    };
}
function buildLegacyGeneratedGeminiAliasPruneSet(parsedEntries) {
    const aliasKeysByNameAndMinor = new Map();
    for (const entry of parsedEntries) {
        if (entry.fork) {
            continue;
        }
        const aliasKey = buildAntigravityAliasKey(entry);
        if (GENERATED_ANTIGRAVITY_ALIAS_MAP.has(aliasKey)) {
            continue;
        }
        const previewInfo = getLegacyGeneratedGeminiPreviewInfo(entry);
        if (!previewInfo) {
            continue;
        }
        const aliasKeysByMinor = aliasKeysByNameAndMinor.get(previewInfo.nameKey) ?? new Map();
        const aliasKeys = aliasKeysByMinor.get(previewInfo.minorVersion) ?? [];
        aliasKeys.push(aliasKey);
        aliasKeysByMinor.set(previewInfo.minorVersion, aliasKeys);
        aliasKeysByNameAndMinor.set(previewInfo.nameKey, aliasKeysByMinor);
    }
    const staleAliasKeys = new Set();
    for (const aliasKeysByMinor of aliasKeysByNameAndMinor.values()) {
        const sortedMinorVersions = [...aliasKeysByMinor.keys()].sort((left, right) => {
            return Number(left) - Number(right);
        });
        const totalAliasCount = sortedMinorVersions.reduce((total, minorVersion) => {
            return total + (aliasKeysByMinor.get(minorVersion)?.length ?? 0);
        }, 0);
        const preservedMinorVersion = Number(sortedMinorVersions[0]) <= MAX_LEGACY_MANUAL_GEMINI_MINOR_VERSION
            ? sortedMinorVersions[0]
            : null;
        const minimumMinorVersionsToPrune = preservedMinorVersion
            ? MIN_STALE_GUESSED_GEMINI_MINOR_VERSIONS
            : MIN_STALE_HIGH_ONLY_GEMINI_MINOR_VERSIONS;
        if (sortedMinorVersions.length < minimumMinorVersionsToPrune) {
            continue;
        }
        if (totalAliasCount <
            sortedMinorVersions.length * MIN_STALE_GUESSED_GEMINI_AVERAGE_VARIANTS_PER_MINOR) {
            continue;
        }
        for (const minorVersion of sortedMinorVersions) {
            if (minorVersion === preservedMinorVersion) {
                continue;
            }
            const aliasKeys = aliasKeysByMinor.get(minorVersion) ?? [];
            for (const aliasKey of aliasKeys) {
                staleAliasKeys.add(aliasKey);
            }
        }
    }
    return staleAliasKeys;
}
function extractPreservedAntigravityAliases(existingAliases, options) {
    if (!existingAliases.trim()) {
        return { yaml: '', prunedLegacyAliasCount: 0 };
    }
    const parsedEntries = parseExistingAntigravityAliases(existingAliases);
    const staleAliasKeys = options?.enableLegacyGeminiStaleCleanup
        ? buildLegacyGeneratedGeminiAliasPruneSet(parsedEntries)
        : new Set();
    const preservedEntries = parsedEntries.filter((entry) => {
        const aliasKey = buildAntigravityAliasKey(entry);
        const generatedEntry = GENERATED_ANTIGRAVITY_ALIAS_MAP.get(aliasKey);
        if (generatedEntry) {
            return Boolean(entry.fork) && !generatedEntry.fork;
        }
        if (entry.fork) {
            return true;
        }
        return !staleAliasKeys.has(aliasKey);
    });
    return {
        yaml: serializeAntigravityAliases(preservedEntries),
        prunedLegacyAliasCount: parsedEntries.filter((entry) => staleAliasKeys.has(buildAntigravityAliasKey(entry))).length,
    };
}
function writeLegacyGeminiAliasCleanupBackup(configPath, existingContent) {
    const backupPath = `${configPath}.pre-v16-gemini-alias-cleanup.bak`;
    if (fs.existsSync(backupPath)) {
        return;
    }
    fs.writeFileSync(backupPath, existingContent, { mode: 0o600 });
}
/**
 * Generate oauth-model-alias YAML section.
 * Merges default Antigravity aliases with any user-added custom aliases.
 */
function generateOAuthModelAliasSection(existingAliases) {
    const aliasEntries = [];
    const aliasIndexByKey = new Map();
    // Start with default aliases.
    for (const alias of DEFAULT_ANTIGRAVITY_ALIASES) {
        addAliasEntry(aliasEntries, aliasIndexByKey, alias);
    }
    // Merge existing user aliases (dedupe by name+alias, not by name only).
    if (existingAliases) {
        const parsed = parseExistingAntigravityAliases(existingAliases);
        for (const alias of parsed) {
            addAliasEntry(aliasEntries, aliasIndexByKey, alias);
        }
    }
    // Expand lightweight compatibility aliases (dot/hyphen + customtools toggle).
    for (const alias of getCompatibilityAliases(aliasEntries)) {
        addAliasEntry(aliasEntries, aliasIndexByKey, alias);
    }
    const entries = aliasEntries
        .map((a) => {
        let entry = `    - name: ${a.name}\n      alias: ${a.alias}`;
        if (a.fork)
            entry += '\n      fork: true';
        return entry;
    })
        .join('\n');
    return `oauth-model-alias:\n  antigravity:\n${entries}`;
}
/**
 * Generate UNIFIED config.yaml content for ALL providers
 * This enables concurrent usage of gemini/codex/agy without config conflicts.
 * CLIProxyAPI routes requests by model name to the appropriate provider.
 *
 * @param port - Server port (default: 8317)
 * @param userApiKeys - User-added API keys to preserve (default: [])
 * @param existingAliases - Existing oauth-model-alias content to merge with defaults
 */
function generateUnifiedConfigContent(port = port_manager_1.CLIPROXY_DEFAULT_PORT, userApiKeys = [], existingAliases) {
    const authDir = (0, path_resolver_1.getAuthDir)(); // Base auth dir - CLIProxyAPI scans subdirectories
    // Convert Windows backslashes to forward slashes for YAML compatibility
    const authDirNormalized = authDir.split(path.sep).join('/');
    // Get logging settings from user config (disabled by default)
    const { loggingToFile, requestLog } = getLoggingSettings();
    const routingStrategy = getRoutingStrategy();
    const sessionAffinityEnabled = getSessionAffinityEnabled();
    const sessionAffinityTtl = getSessionAffinityTtl();
    const managementPanelRepository = getManagementPanelRepository();
    // Get effective auth tokens (respects user customization)
    const effectiveApiKey = (0, auth_token_manager_1.getEffectiveApiKey)();
    const effectiveSecret = (0, auth_token_manager_1.getEffectiveManagementSecret)();
    // Build api-keys section with internal key + preserved user keys.
    // Docker upgrades may temporarily keep the historical default key as a
    // compatibility grace key; the marker file controls when that is active.
    const allApiKeys = Array.from(new Set([effectiveApiKey, ...(0, docker_key_rotation_1.getActiveDockerLegacyApiKeys)(), ...userApiKeys]));
    const apiKeysYaml = allApiKeys.map((key) => `  - "${key}"`).join('\n');
    // Unified config with enhanced CLIProxyAPI features
    const config = `# CLIProxyAPI config generated by CCS v${exports.CLIPROXY_CONFIG_VERSION}
# Supports: gemini, codex, agy, qwen, iflow (concurrent usage)
# Generated: ${new Date().toISOString()}
#
# This config is auto-managed by CCS. Manual edits may be overwritten.
# Use 'ccs doctor' to regenerate with latest settings.

# =============================================================================
# Server Settings
# =============================================================================

port: ${port}
debug: false

# =============================================================================
# Logging
# =============================================================================
# WARNING: Logs can grow to several GB if enabled!
# To enable logging, edit ~/.ccs/config.yaml:
#   cliproxy:
#     logging:
#       enabled: true
#       request_log: true
# Then run 'ccs doctor --fix' to regenerate this config.
# Use 'ccs cleanup' to remove old logs.

# Write logs to file (stored in ~/.ccs/cliproxy/logs/)
logging-to-file: ${loggingToFile}

# Log individual API requests for debugging/analytics
request-log: ${requestLog}

# =============================================================================
# Dashboard & Management
# =============================================================================

# Enable usage statistics for CCS dashboard analytics
usage-statistics-enabled: true

# Remote management API for CCS dashboard integration
remote-management:
  allow-remote: true
  secret-key: "${effectiveSecret}"
  disable-control-panel: false
  panel-github-repository: ${quoteYamlString(managementPanelRepository)}

# =============================================================================
# Reliability & Quota Management
# =============================================================================

# Disable quota cooldown scheduling for stability
disable-cooling: true

# Auto-retry on transient errors (403, 408, 500, 502, 503, 504)
request-retry: 0
max-retry-interval: 0

# Auto-switch accounts on quota exceeded (429)
# This enables seamless multi-account rotation when rate limited
quota-exceeded:
  switch-project: true
  switch-preview-model: true

# Credential selection strategy when multiple matching accounts are available
routing:
  strategy: ${routingStrategy}
  session-affinity: ${sessionAffinityEnabled}
  session-affinity-ttl: "${sessionAffinityTtl}"

# =============================================================================
# Authentication
# =============================================================================

# API keys for CCS and user-added external requests
# NOTE: User-added keys are preserved across CCS updates (fix for issue #200)
api-keys:
${apiKeysYaml}

# OAuth tokens directory (auto-discovered by CLIProxyAPI)
auth-dir: "${authDirNormalized}"
${generateOAuthModelAliasSection(existingAliases)}
`;
    return config;
}
/**
 * Generate unified config.yaml file (supports all providers concurrently)
 * Only regenerates if config doesn't exist.
 * @returns Path to config file
 */
function generateConfig(provider, port = port_manager_1.CLIPROXY_DEFAULT_PORT) {
    const configPath = (0, path_resolver_1.getConfigPathForPort)(port);
    // Ensure provider auth directory exists
    const authDir = (0, path_resolver_1.getProviderAuthDir)(provider);
    fs.mkdirSync(path.dirname(configPath), { recursive: true });
    fs.mkdirSync(authDir, { recursive: true, mode: 0o700 });
    // Only generate config if it doesn't exist (unified config serves all providers)
    if (!fs.existsSync(configPath)) {
        const configContent = generateUnifiedConfigContent(port);
        fs.writeFileSync(configPath, configContent, { mode: 0o600 });
    }
    return configPath;
}
exports.generateConfig = generateConfig;
/**
 * Parse user-added API keys from existing config content.
 * Extracts all keys except the internal CCS key for preservation.
 *
 * @param content - Existing config.yaml content
 * @returns Array of user-added API keys (excludes CCS_INTERNAL_API_KEY)
 */
function parseUserApiKeys(content) {
    const userKeys = [];
    // Find the api-keys section by looking for lines starting with "  - " after "api-keys:"
    // Normalize line endings first
    const normalizedContent = content.replace(/\r\n/g, '\n');
    // Find the api-keys: line and extract all subsequent key entries
    const lines = normalizedContent.split('\n');
    let inApiKeysSection = false;
    for (const line of lines) {
        // Check if this is the start of api-keys section
        if (line.match(/^api-keys:\s*$/)) {
            inApiKeysSection = true;
            continue;
        }
        // If we're in the api-keys section, look for key entries
        if (inApiKeysSection) {
            // Key entries are indented with "  - " or similar
            const keyMatch = line.match(/^\s+-\s*"([^"]*)"/);
            if (keyMatch) {
                const key = keyMatch[1];
                // Exclude the internal CCS key and empty strings
                if (key && key !== exports.CCS_INTERNAL_API_KEY) {
                    userKeys.push(key);
                }
            }
            else if (line.match(/^\S/) && line.trim().length > 0) {
                // Non-indented line that's not empty means we've left the api-keys section
                break;
            }
            // Continue for blank lines or other indented content
        }
    }
    return userKeys;
}
exports.parseUserApiKeys = parseUserApiKeys;
/**
 * Extract a YAML section from config content by key name.
 * Returns the raw lines (including indented children) or empty string.
 */
function extractYamlSection(content, sectionKey) {
    const lines = content.split('\n');
    const sectionLines = [];
    let inSection = false;
    for (const line of lines) {
        if (line.startsWith(`${sectionKey}:`)) {
            inSection = true;
            continue; // Skip the key line itself
        }
        if (inSection) {
            // Section ends at next top-level key (skip comments and blank lines)
            if (line.match(/^\S/) && !line.startsWith('#') && line.trim().length > 0) {
                break;
            }
            sectionLines.push(line);
        }
    }
    // Strip leading/trailing blank lines but preserve indentation
    return sectionLines.join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
}
/**
 * Force regenerate config.yaml with latest settings.
 * Preserves user-added API keys, claude-api-key section, and port settings.
 *
 * @param port - Default port to use if not found in existing config
 * @returns Path to new config file
 */
function regenerateConfig(port = port_manager_1.CLIPROXY_DEFAULT_PORT, options) {
    const configPath = options?.configPath ?? (0, path_resolver_1.getConfigPathForPort)(port);
    const authDir = options?.authDir ?? (0, path_resolver_1.getAuthDir)();
    // Preserve user settings from existing config
    let effectivePort = port;
    let userApiKeys = [];
    let existingAliases = '';
    const preservedSections = [];
    if (fs.existsSync(configPath)) {
        try {
            const content = fs.readFileSync(configPath, 'utf-8');
            // Preserve port setting
            const portMatch = content.match(/^port:\s*(\d+)/m);
            if (portMatch) {
                effectivePort = parseInt(portMatch[1], 10);
            }
            // Preserve user-added API keys (fix for issue #200)
            userApiKeys = parseUserApiKeys(content);
            // Preserve AI provider sections managed outside the generated defaults.
            for (const familyId of types_1.AI_PROVIDER_FAMILY_IDS) {
                const sectionBody = extractYamlSection(content, familyId);
                if (sectionBody) {
                    preservedSections.push({
                        key: familyId,
                        body: sectionBody,
                    });
                }
            }
            // Preserve user customizations while pruning legacy generated Gemini preview noise.
            const existingConfigVersion = getConfigVersionFromContent(content);
            const preservedAliases = extractPreservedAntigravityAliases(extractYamlSection(content, 'oauth-model-alias'), {
                enableLegacyGeminiStaleCleanup: existingConfigVersion === null ||
                    existingConfigVersion < LEGACY_GEMINI_STALE_ALIAS_MIGRATION_VERSION,
            });
            existingAliases = preservedAliases.yaml;
            if (preservedAliases.prunedLegacyAliasCount > 0) {
                writeLegacyGeminiAliasCleanupBackup(configPath, content);
            }
        }
        catch {
            // Use defaults if reading fails
        }
        // Delete existing config
        fs.unlinkSync(configPath);
    }
    // Ensure directories exist
    fs.mkdirSync(path.dirname(configPath), { recursive: true });
    fs.mkdirSync(authDir, { recursive: true, mode: 0o700 });
    // Generate fresh config with preserved user API keys and aliases
    let configContent = generateUnifiedConfigContent(effectivePort, userApiKeys, existingAliases);
    // Re-append managed top-level sections that are not part of the generated defaults.
    for (const section of preservedSections) {
        configContent += `${section.key}:\n${section.body}\n`;
    }
    fs.writeFileSync(configPath, configContent, { mode: 0o600 });
    return configPath;
}
exports.regenerateConfig = regenerateConfig;
/**
 * Check if config needs regeneration (version mismatch)
 * @returns true if config should be regenerated
 */
function configNeedsRegeneration(port = port_manager_1.CLIPROXY_DEFAULT_PORT) {
    const configPath = (0, path_resolver_1.getConfigPathForPort)(port);
    if (!fs.existsSync(configPath)) {
        return false; // Will be created on first use
    }
    try {
        const content = fs.readFileSync(configPath, 'utf-8');
        // Check for version marker
        const configVersion = getConfigVersionFromContent(content);
        if (configVersion === null) {
            return true; // No version marker = old config
        }
        if (configVersion < exports.CLIPROXY_CONFIG_VERSION) {
            return true;
        }
        return parseManagementPanelRepository(content) !== getManagementPanelRepository();
    }
    catch {
        return true; // Error reading = regenerate
    }
}
exports.configNeedsRegeneration = configNeedsRegeneration;
/**
 * Check if config exists for port
 */
function configExists(port = port_manager_1.CLIPROXY_DEFAULT_PORT) {
    return fs.existsSync((0, path_resolver_1.getConfigPathForPort)(port));
}
exports.configExists = configExists;
/**
 * Delete config file for specific port
 */
function deleteConfigForPort(port) {
    const configPath = (0, path_resolver_1.getConfigPathForPort)(port);
    if (fs.existsSync(configPath)) {
        fs.unlinkSync(configPath);
    }
}
exports.deleteConfigForPort = deleteConfigForPort;
/**
 * Delete config file (default port)
 */
function deleteConfig() {
    deleteConfigForPort(port_manager_1.CLIPROXY_DEFAULT_PORT);
}
exports.deleteConfig = deleteConfig;
//# sourceMappingURL=generator.js.map