"use strict";
/**
 * Route Helpers - Shared utility functions for route handlers
 */
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.createRouteErrorHelpers = exports.parseTarget = exports.validateFilePath = exports.updateSettingsFile = exports.createSettingsFile = exports.isConfigured = exports.writeConfig = exports.readConfigSafe = exports.isAnthropicDirectProfile = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const config_manager_1 = require("../../utils/config-manager");
const helpers_1 = require("../../utils/helpers");
const claude_config_path_1 = require("../../utils/claude-config-path");
const droid_provider_1 = require("../../targets/droid-provider");
const provider_capabilities_1 = require("../../cliproxy/provider-capabilities");
const model_id_normalizer_1 = require("../../cliproxy/ai-providers/model-id-normalizer");
const target_metadata_1 = require("../../targets/target-metadata");
const error_types_1 = require("../../errors/error-types");
const config_loader_facade_1 = require("../../config/config-loader-facade");
function resolveProviderFromBaseUrl(baseUrl) {
    if (typeof baseUrl !== 'string' || baseUrl.trim().length === 0) {
        return null;
    }
    try {
        const parsed = new URL(baseUrl);
        const extracted = (0, model_id_normalizer_1.extractProviderFromPathname)(parsed.pathname);
        return extracted ? (0, provider_capabilities_1.mapExternalProviderName)(extracted) : null;
    }
    catch {
        const extracted = (0, model_id_normalizer_1.extractProviderFromPathname)(baseUrl);
        return extracted ? (0, provider_capabilities_1.mapExternalProviderName)(extracted) : null;
    }
}
function resolveProviderForModelCanonicalization(baseUrl, providerHint) {
    const fromBaseUrl = resolveProviderFromBaseUrl(baseUrl);
    if (fromBaseUrl) {
        return fromBaseUrl;
    }
    if (typeof providerHint === 'string' && providerHint.trim().length > 0) {
        const fromProviderHint = (0, provider_capabilities_1.mapExternalProviderName)(providerHint);
        if (fromProviderHint) {
            return fromProviderHint;
        }
    }
    return null;
}
function getDeniedModelReasonForProvider(provider, values) {
    if (!provider)
        return null;
    for (const value of values) {
        if (typeof value !== 'string' || value.trim().length === 0)
            continue;
        const deniedReason = (0, model_id_normalizer_1.getDeniedModelIdReasonForProvider)(value, provider);
        if (deniedReason)
            return deniedReason;
    }
    return null;
}
function canonicalizeModelForProvider(provider, value) {
    if (typeof value !== 'string')
        return value;
    if (!provider || value.trim().length === 0)
        return value;
    return (0, model_id_normalizer_1.canonicalizeModelIdForProvider)(value, provider);
}
function isOpenRouterUrl(baseUrl) {
    return baseUrl.toLowerCase().includes('openrouter.ai');
}
function isAnthropicDirectProfile(baseUrl, apiKey) {
    const normalizedBaseUrl = baseUrl?.trim().toLowerCase() || '';
    const normalizedApiKey = apiKey?.trim() || '';
    return normalizedApiKey.startsWith('sk-ant-') || normalizedBaseUrl.includes('api.anthropic.com');
}
exports.isAnthropicDirectProfile = isAnthropicDirectProfile;
/**
 * Read config safely with fallback.
 * Uses loadConfigSafe which supports both unified (config.yaml) and legacy (config.json).
 */
function readConfigSafe() {
    try {
        return (0, config_loader_facade_1.loadConfigSafe)();
    }
    catch {
        return { profiles: {} };
    }
}
exports.readConfigSafe = readConfigSafe;
/**
 * Write config atomically
 */
function writeConfig(config) {
    const configPath = (0, config_manager_1.getConfigPath)();
    const tempPath = configPath + '.tmp';
    fs.writeFileSync(tempPath, JSON.stringify(config, null, 2) + '\n');
    fs.renameSync(tempPath, configPath);
}
exports.writeConfig = writeConfig;
/**
 * Check if profile is configured (has valid settings file)
 */
function isConfigured(profileName, config) {
    const settingsPath = config.profiles[profileName];
    if (!settingsPath)
        return false;
    try {
        const expandedPath = (0, helpers_1.expandPath)(settingsPath);
        if (!fs.existsSync(expandedPath))
            return false;
        const settings = (0, config_loader_facade_1.loadSettings)(expandedPath);
        // Proxy mode: BASE_URL + AUTH_TOKEN; Native mode: ANTHROPIC_API_KEY only
        return !!((settings.env?.ANTHROPIC_BASE_URL && settings.env?.ANTHROPIC_AUTH_TOKEN) ||
            settings.env?.ANTHROPIC_API_KEY);
    }
    catch {
        return false;
    }
}
exports.isConfigured = isConfigured;
/**
 * Create settings file for profile
 */
function createSettingsFile(name, baseUrl, apiKey, models = {}, provider) {
    const settingsPath = path.join((0, config_loader_facade_1.getCcsDir)(), `${name}.settings.json`);
    const { model, opusModel, sonnetModel, haikuModel } = models;
    const providerForModelCanonicalization = resolveProviderForModelCanonicalization(baseUrl, provider);
    const canonicalModel = canonicalizeModelForProvider(providerForModelCanonicalization, model);
    const canonicalOpusModel = canonicalizeModelForProvider(providerForModelCanonicalization, opusModel);
    const canonicalSonnetModel = canonicalizeModelForProvider(providerForModelCanonicalization, sonnetModel);
    const canonicalHaikuModel = canonicalizeModelForProvider(providerForModelCanonicalization, haikuModel);
    const deniedReason = getDeniedModelReasonForProvider(providerForModelCanonicalization, [
        canonicalModel,
        canonicalOpusModel,
        canonicalSonnetModel,
        canonicalHaikuModel,
    ]);
    if (deniedReason) {
        throw new error_types_1.ValidationError(deniedReason, 'model');
    }
    const droidProvider = (0, droid_provider_1.resolveDroidProvider)({
        provider,
        baseUrl,
        model: canonicalModel,
    });
    const normalizedBaseUrl = baseUrl.trim();
    const normalizedApiKey = apiKey.trim();
    const isNative = isAnthropicDirectProfile(normalizedBaseUrl, normalizedApiKey);
    const settings = {
        env: {
            ...(isNative
                ? { ANTHROPIC_API_KEY: normalizedApiKey }
                : {
                    ANTHROPIC_BASE_URL: normalizedBaseUrl,
                    ANTHROPIC_AUTH_TOKEN: normalizedApiKey,
                    ...(isOpenRouterUrl(normalizedBaseUrl) && { ANTHROPIC_API_KEY: '' }),
                }),
            ...(canonicalModel && { ANTHROPIC_MODEL: canonicalModel }),
            ...(canonicalOpusModel && { ANTHROPIC_DEFAULT_OPUS_MODEL: canonicalOpusModel }),
            ...(canonicalSonnetModel && { ANTHROPIC_DEFAULT_SONNET_MODEL: canonicalSonnetModel }),
            ...(canonicalHaikuModel && { ANTHROPIC_DEFAULT_HAIKU_MODEL: canonicalHaikuModel }),
            CCS_DROID_PROVIDER: droidProvider,
        },
    };
    fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
    fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
    return `~/.ccs/${name}.settings.json`;
}
exports.createSettingsFile = createSettingsFile;
/**
 * Update settings file
 */
function updateSettingsFile(name, updates) {
    const settingsPath = path.join((0, config_loader_facade_1.getCcsDir)(), `${name}.settings.json`);
    if (!fs.existsSync(settingsPath)) {
        throw new Error('Settings file not found');
    }
    const settings = (0, config_loader_facade_1.loadSettings)(settingsPath);
    const currentBaseUrl = settings.env?.ANTHROPIC_BASE_URL?.trim() || '';
    const currentApiKey = settings.env?.ANTHROPIC_API_KEY?.trim() || settings.env?.ANTHROPIC_AUTH_TOKEN?.trim() || '';
    const nextBaseUrl = updates.baseUrl !== undefined ? updates.baseUrl.trim() : currentBaseUrl;
    const nextApiKey = updates.apiKey !== undefined ? updates.apiKey.trim() : currentApiKey;
    const isNative = isAnthropicDirectProfile(nextBaseUrl, nextApiKey);
    if (!nextApiKey) {
        throw new error_types_1.ValidationError('apiKey cannot be empty', 'apiKey');
    }
    if (!isNative && nextBaseUrl.length === 0) {
        throw new error_types_1.ValidationError('baseUrl cannot be empty', 'baseUrl');
    }
    const providerForValidation = resolveProviderForModelCanonicalization(nextBaseUrl, updates.provider) ??
        resolveProviderForModelCanonicalization(settings.env?.ANTHROPIC_BASE_URL, updates.provider ?? settings.env?.CCS_DROID_PROVIDER);
    const canonicalModel = updates.model !== undefined
        ? canonicalizeModelForProvider(providerForValidation, updates.model)
        : undefined;
    const canonicalOpusModel = updates.opusModel !== undefined
        ? canonicalizeModelForProvider(providerForValidation, updates.opusModel)
        : undefined;
    const canonicalSonnetModel = updates.sonnetModel !== undefined
        ? canonicalizeModelForProvider(providerForValidation, updates.sonnetModel)
        : undefined;
    const canonicalHaikuModel = updates.haikuModel !== undefined
        ? canonicalizeModelForProvider(providerForValidation, updates.haikuModel)
        : undefined;
    const deniedReason = getDeniedModelReasonForProvider(providerForValidation, [
        canonicalModel !== undefined ? canonicalModel : settings.env?.ANTHROPIC_MODEL,
        canonicalOpusModel !== undefined
            ? canonicalOpusModel
            : settings.env?.ANTHROPIC_DEFAULT_OPUS_MODEL,
        canonicalSonnetModel !== undefined
            ? canonicalSonnetModel
            : settings.env?.ANTHROPIC_DEFAULT_SONNET_MODEL,
        canonicalHaikuModel !== undefined
            ? canonicalHaikuModel
            : settings.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL,
    ]);
    if (deniedReason) {
        throw new error_types_1.ValidationError(deniedReason, 'model');
    }
    settings.env = settings.env || {};
    if (isNative) {
        delete settings.env.ANTHROPIC_BASE_URL;
        delete settings.env.ANTHROPIC_AUTH_TOKEN;
        settings.env.ANTHROPIC_API_KEY = nextApiKey;
    }
    else {
        settings.env.ANTHROPIC_BASE_URL = nextBaseUrl;
        settings.env.ANTHROPIC_AUTH_TOKEN = nextApiKey;
        if (isOpenRouterUrl(nextBaseUrl)) {
            settings.env.ANTHROPIC_API_KEY = '';
        }
        else {
            delete settings.env.ANTHROPIC_API_KEY;
        }
    }
    if (updates.model !== undefined) {
        settings.env = settings.env || {};
        if (canonicalModel) {
            settings.env.ANTHROPIC_MODEL = canonicalModel;
        }
        else {
            delete settings.env.ANTHROPIC_MODEL;
        }
    }
    // Handle model mapping fields
    if (updates.opusModel !== undefined) {
        settings.env = settings.env || {};
        if (canonicalOpusModel) {
            settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = canonicalOpusModel;
        }
        else {
            delete settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
        }
    }
    if (updates.sonnetModel !== undefined) {
        settings.env = settings.env || {};
        if (canonicalSonnetModel) {
            settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = canonicalSonnetModel;
        }
        else {
            delete settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
        }
    }
    if (updates.haikuModel !== undefined) {
        settings.env = settings.env || {};
        if (canonicalHaikuModel) {
            settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = canonicalHaikuModel;
        }
        else {
            delete settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
        }
    }
    // Handle extra models (comma-separated string)
    if (updates.extraModels !== undefined) {
        settings.env = settings.env || {};
        const normalized = typeof updates.extraModels === 'string' ? updates.extraModels.trim() : '';
        if (normalized.length > 0) {
            settings.env.ANTHROPIC_EXTRA_MODELS = normalized;
        }
        else {
            delete settings.env.ANTHROPIC_EXTRA_MODELS;
        }
    }
    if (updates.provider !== undefined ||
        updates.baseUrl !== undefined ||
        updates.model !== undefined ||
        settings.env?.CCS_DROID_PROVIDER) {
        settings.env = settings.env || {};
        const resolvedProvider = (0, droid_provider_1.resolveDroidProvider)({
            provider: updates.provider ?? settings.env.CCS_DROID_PROVIDER,
            baseUrl: nextBaseUrl,
            model: canonicalModel ?? settings.env.ANTHROPIC_MODEL,
        });
        settings.env.CCS_DROID_PROVIDER = resolvedProvider;
    }
    fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
}
exports.updateSettingsFile = updateSettingsFile;
/**
 * Security: Validate file path is within allowed directories
 * - ~/.ccs/ directory: read/write allowed
 * - ~/.claude/settings.json: read-only
 */
function normalizePathForComparison(filePath) {
    const normalized = path.resolve(path.normalize(filePath));
    return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
}
function isPathWithin(basePath, targetPath) {
    const relative = path.relative(basePath, targetPath);
    return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}
function isSymlinkPath(filePath) {
    try {
        return fs.lstatSync(filePath).isSymbolicLink();
    }
    catch (error) {
        const nodeError = error;
        if (nodeError.code === 'ENOENT' || nodeError.code === 'ENOTDIR') {
            return false;
        }
        return true;
    }
}
function hasSymlinkSegment(basePath, targetPath) {
    const relative = path.relative(basePath, targetPath);
    if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) {
        return false;
    }
    let currentPath = basePath;
    const segments = relative.split(path.sep).filter(Boolean);
    for (const segment of segments) {
        currentPath = path.join(currentPath, segment);
        if (isSymlinkPath(currentPath)) {
            return true;
        }
    }
    return false;
}
function validateFilePath(filePath) {
    const expandedPath = (0, helpers_1.expandPath)(filePath);
    const resolvedPath = path.resolve(path.normalize(expandedPath));
    const resolvedCcsDir = path.resolve(path.normalize((0, config_loader_facade_1.getCcsDir)()));
    const resolvedClaudeSettingsPath = path.resolve(path.normalize((0, claude_config_path_1.getClaudeSettingsPath)()));
    const normalizedPath = normalizePathForComparison(resolvedPath);
    const ccsDir = normalizePathForComparison(resolvedCcsDir);
    const claudeSettingsPath = normalizePathForComparison(resolvedClaudeSettingsPath);
    // Check if path is within ~/.ccs/
    if (isPathWithin(ccsDir, normalizedPath)) {
        if (hasSymlinkSegment(resolvedCcsDir, resolvedPath)) {
            return { valid: false, readonly: false, error: 'Access to this path is not allowed' };
        }
        // Block access to sensitive subdirectories
        const relativePath = path.relative(ccsDir, normalizedPath);
        const pathSegments = relativePath.split(path.sep).filter(Boolean);
        if (pathSegments.includes('.git') || pathSegments.includes('node_modules')) {
            return { valid: false, readonly: false, error: 'Access to this path is not allowed' };
        }
        return { valid: true, readonly: false };
    }
    // Allow read-only access to ~/.claude/settings.json
    if (normalizedPath === claudeSettingsPath) {
        if (isSymlinkPath(resolvedClaudeSettingsPath)) {
            return { valid: false, readonly: false, error: 'Access to this path is not allowed' };
        }
        return { valid: true, readonly: true };
    }
    return { valid: false, readonly: false, error: 'Access to this path is not allowed' };
}
exports.validateFilePath = validateFilePath;
/**
 * Parse and validate a persisted target param. Returns null if invalid/absent.
 * Shared by profile-routes and variant-routes.
 */
function parseTarget(rawTarget) {
    if (rawTarget === undefined || rawTarget === null || rawTarget === '') {
        return null;
    }
    if (typeof rawTarget !== 'string') {
        return null;
    }
    const normalized = rawTarget.trim().toLowerCase();
    if ((0, target_metadata_1.isPersistedTargetType)(normalized)) {
        return normalized;
    }
    return null;
}
exports.parseTarget = parseTarget;
/**
 * Create route-specific error helpers with a log prefix.
 * Eliminates duplicate logRouteError/respondInternalError in each route file.
 */
function createRouteErrorHelpers(prefix) {
    function logRouteError(context, error) {
        if (error instanceof Error) {
            console.error(`[${prefix}] ${context}: ${error.message}`);
            return;
        }
        console.error(`[${prefix}] ${context}: unknown error`);
    }
    function respondInternalError(res, error, fallbackMessage, statusCode = 500) {
        logRouteError(fallbackMessage, error);
        res.status(statusCode).json({ error: fallbackMessage });
    }
    return { logRouteError, respondInternalError };
}
exports.createRouteErrorHelpers = createRouteErrorHelpers;
//# sourceMappingURL=route-helpers.js.map