"use strict";
/**
 * Profile Routes - CRUD operations for user profiles
 *
 * Uses unified config (config.yaml) when available, falls back to legacy (config.json).
 * Note: Account routes have been moved to account-routes.ts
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseTarget = void 0;
const express_1 = require("express");
const reserved_names_1 = require("../../config/reserved-names");
const services_1 = require("../../api/services");
const droid_provider_1 = require("../../targets/droid-provider");
const target_metadata_1 = require("../../targets/target-metadata");
const provider_capabilities_1 = require("../../cliproxy/provider-capabilities");
const route_helpers_1 = require("./route-helpers");
const router = (0, express_1.Router)();
function isDenylistError(message) {
    return typeof message === 'string' && message.toLowerCase().includes('denylist');
}
function getUnknownKeys(payload, allowedKeys) {
    const allowed = new Set(allowedKeys);
    return Object.keys(payload).filter((key) => !allowed.has(key));
}
function validatePayloadShape(body, allowedKeys) {
    if (!body || typeof body !== 'object' || Array.isArray(body)) {
        return { ok: false, error: 'Request body must be a JSON object' };
    }
    const payload = body;
    const unknownKeys = getUnknownKeys(payload, allowedKeys);
    if (unknownKeys.length > 0) {
        return { ok: false, error: `Unknown profile field(s): ${unknownKeys.join(', ')}` };
    }
    return { ok: true, payload };
}
// ==================== Profile CRUD ====================
/**
 * GET /api/profiles - List all profiles
 */
router.get('/', (_req, res) => {
    try {
        const result = (0, services_1.listApiProfiles)();
        // Map isConfigured -> configured for UI compatibility
        const profiles = result.profiles.map((p) => ({
            name: p.name,
            settingsPath: p.settingsPath,
            configured: p.isConfigured,
            target: p.target,
            cliproxyBridge: p.cliproxyBridge ?? null,
        }));
        res.json({ profiles });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
router.get('/cliproxy-bridge/providers', (_req, res) => {
    try {
        res.json({ providers: (0, services_1.listCliproxyBridgeProviders)() });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
router.get('/local-runtime-readiness', async (_req, res) => {
    try {
        res.json({ runtimes: await (0, services_1.getLocalRuntimeReadiness)() });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
router.post('/cliproxy-bridge', (req, res) => {
    const shape = validatePayloadShape(req.body, ['provider', 'name', 'target']);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const provider = typeof shape.payload.provider === 'string' ? shape.payload.provider.trim() : '';
    if (!(0, provider_capabilities_1.isCLIProxyProvider)(provider)) {
        res.status(400).json({ error: 'Invalid provider. Expected a supported CLIProxy provider ID.' });
        return;
    }
    const target = (0, route_helpers_1.parseTarget)(shape.payload.target);
    if (shape.payload.target !== undefined && target === null) {
        res.status(400).json({ error: `Invalid target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}` });
        return;
    }
    const result = (0, services_1.createCliproxyBridgeProfile)(provider, {
        name: typeof shape.payload.name === 'string' ? shape.payload.name : undefined,
        target: target || 'claude',
    });
    if (!result.success || !result.name) {
        const errorMessage = result.error || 'Failed to create CLIProxy bridge profile';
        res.status(errorMessage.toLowerCase().includes('already exists') ? 409 : 400).json({
            error: errorMessage,
        });
        return;
    }
    res.status(201).json({
        name: result.name,
        settingsPath: result.settingsFile,
        target: result.target || 'claude',
        cliproxyBridge: result.cliproxyBridge ?? null,
    });
});
/**
 * POST /api/profiles - Create new profile
 */
router.post('/', (req, res) => {
    const shape = validatePayloadShape(req.body, [
        'name',
        'baseUrl',
        'apiKey',
        'model',
        'extraModels',
        'opusModel',
        'sonnetModel',
        'haikuModel',
        'target',
        'droidProvider',
        'provider',
    ]);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const { name, baseUrl, apiKey, model, extraModels, opusModel, sonnetModel, haikuModel, target } = req.body;
    const providerHint = req.body?.droidProvider ?? req.body?.provider;
    const parsedProvider = (0, droid_provider_1.normalizeDroidProvider)(providerHint);
    const normalizedBaseUrl = typeof baseUrl === 'string' ? baseUrl.trim() : '';
    const normalizedApiKey = typeof apiKey === 'string' ? apiKey.trim() : '';
    const allowsEmptyBaseUrl = (0, route_helpers_1.isAnthropicDirectProfile)(normalizedBaseUrl, normalizedApiKey);
    const parsedTarget = (0, route_helpers_1.parseTarget)(target);
    if (target !== undefined && parsedTarget === null) {
        res.status(400).json({ error: `Invalid target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}` });
        return;
    }
    if (providerHint !== undefined && parsedProvider === null) {
        res.status(400).json({
            error: 'Invalid droid provider. Expected: anthropic, openai, or generic-chat-completion-api',
        });
        return;
    }
    if (!name || !normalizedApiKey || (!normalizedBaseUrl && !allowsEmptyBaseUrl)) {
        res.status(400).json({
            error: 'Missing required fields: name, apiKey, and baseUrl for proxy profiles',
        });
        return;
    }
    // Validate reserved names
    if ((0, reserved_names_1.isReservedName)(name)) {
        res.status(400).json({
            error: `Profile name '${name}' is reserved`,
            reserved: reserved_names_1.RESERVED_PROFILE_NAMES,
        });
        return;
    }
    // Check if profile already exists (uses unified config when available)
    if ((0, services_1.apiProfileExists)(name)) {
        res.status(409).json({ error: 'Profile already exists' });
        return;
    }
    // Create profile using unified-config-aware service
    const parsedExtraModels = typeof extraModels === 'string' && extraModels.trim()
        ? extraModels
            .split(',')
            .map((m) => m.trim())
            .filter((m) => m.length > 0)
        : undefined;
    const result = (0, services_1.createApiProfile)(name, normalizedBaseUrl, normalizedApiKey, {
        default: model || '',
        opus: opusModel || model || '',
        sonnet: sonnetModel || model || '',
        haiku: haikuModel || model || '',
    }, parsedTarget || 'claude', parsedProvider || undefined, parsedExtraModels);
    if (!result.success) {
        const errorMessage = result.error || 'Failed to create profile';
        res.status(isDenylistError(errorMessage) ? 400 : 500).json({ error: errorMessage });
        return;
    }
    res.status(201).json({
        name,
        settingsPath: result.settingsFile,
        target: parsedTarget || 'claude',
        cliproxyBridge: null,
    });
});
/**
 * GET /api/profiles/orphans - Discover orphan ~/.ccs/*.settings.json files
 */
router.get('/orphans', (_req, res) => {
    try {
        const result = (0, services_1.discoverApiProfileOrphans)();
        res.json(result);
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * POST /api/profiles/orphans/register - Register discovered orphan settings
 */
router.post('/orphans/register', (req, res) => {
    const shape = validatePayloadShape(req.body ?? {}, ['names', 'target', 'force']);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const payload = shape.payload;
    let names;
    if (payload.names !== undefined) {
        if (!Array.isArray(payload.names)) {
            res.status(400).json({ error: 'names must be an array of profile names' });
            return;
        }
        const invalidNameEntry = payload.names.find((value) => typeof value !== 'string' || value.trim().length === 0);
        if (invalidNameEntry !== undefined) {
            res.status(400).json({ error: 'names must contain non-empty strings only' });
            return;
        }
        names = payload.names.map((value) => value.trim());
        const invalidName = names.find((name) => (0, services_1.validateApiName)(name) !== null);
        if (invalidName) {
            res.status(400).json({ error: `Invalid profile name in names: ${invalidName}` });
            return;
        }
    }
    const target = (0, route_helpers_1.parseTarget)(payload.target);
    const force = payload.force === true;
    if (payload.target !== undefined && target === null) {
        res.status(400).json({ error: `Invalid target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}` });
        return;
    }
    try {
        const result = (0, services_1.registerApiProfileOrphans)({
            names,
            target: target || 'claude',
            force,
        });
        res.json(result);
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
/**
 * POST /api/profiles/:name/copy - Duplicate an API profile
 */
router.post('/:name/copy', (req, res) => {
    const shape = validatePayloadShape(req.body, ['destination', 'target', 'force']);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const { name } = req.params;
    const sourceNameError = (0, services_1.validateApiName)(name);
    if (sourceNameError) {
        res.status(400).json({ error: sourceNameError });
        return;
    }
    const destination = shape.payload.destination;
    const target = (0, route_helpers_1.parseTarget)(shape.payload.target);
    const force = shape.payload.force === true;
    if (typeof destination !== 'string' || destination.trim().length === 0) {
        res.status(400).json({ error: 'destination is required' });
        return;
    }
    if (shape.payload.target !== undefined && target === null) {
        res.status(400).json({ error: `Invalid target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}` });
        return;
    }
    const result = (0, services_1.copyApiProfile)(name, destination.trim(), { target: target || undefined, force });
    if (!result.success) {
        res.status(400).json({ error: result.error || 'Failed to copy profile' });
        return;
    }
    res.status(201).json(result);
});
/**
 * POST /api/profiles/:name/export - Export profile as a portable bundle
 */
router.post('/:name/export', (req, res) => {
    const shape = validatePayloadShape(req.body ?? {}, ['includeSecrets']);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const { name } = req.params;
    const profileNameError = (0, services_1.validateApiName)(name);
    if (profileNameError) {
        res.status(400).json({ error: profileNameError });
        return;
    }
    const includeSecrets = shape.payload.includeSecrets === true;
    const result = (0, services_1.exportApiProfile)(name, includeSecrets);
    if (!result.success || !result.bundle) {
        res.status(400).json({ error: result.error || 'Failed to export profile' });
        return;
    }
    res.json(result);
});
/**
 * POST /api/profiles/import - Import profile bundle into local registry
 */
router.post('/import', (req, res) => {
    const shape = validatePayloadShape(req.body, ['bundle', 'name', 'target', 'force']);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const target = (0, route_helpers_1.parseTarget)(shape.payload.target);
    if (shape.payload.target !== undefined && target === null) {
        res.status(400).json({ error: `Invalid target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}` });
        return;
    }
    const bundle = shape.payload.bundle;
    if (!bundle || typeof bundle !== 'object' || Array.isArray(bundle)) {
        res.status(400).json({ error: 'bundle must be a JSON object' });
        return;
    }
    const bundleTarget = bundle.profile?.target;
    if (bundleTarget !== undefined && (0, route_helpers_1.parseTarget)(bundleTarget) === null) {
        res.status(400).json({
            error: `Invalid bundle profile target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}`,
        });
        return;
    }
    const result = (0, services_1.importApiProfileBundle)(bundle, {
        name: typeof shape.payload.name === 'string' ? shape.payload.name : undefined,
        target: target || undefined,
        force: shape.payload.force === true,
    });
    if (!result.success) {
        res.status(400).json({
            error: result.error || 'Failed to import profile',
            validation: result.validation,
        });
        return;
    }
    res.status(201).json(result);
});
/**
 * PUT /api/profiles/:name - Update profile
 */
router.put('/:name', (req, res) => {
    const shape = validatePayloadShape(req.body, [
        'baseUrl',
        'apiKey',
        'model',
        'extraModels',
        'opusModel',
        'sonnetModel',
        'haikuModel',
        'target',
        'droidProvider',
        'provider',
    ]);
    if (!shape.ok) {
        res.status(400).json({ error: shape.error });
        return;
    }
    const { name } = req.params;
    const { baseUrl, apiKey, model, extraModels, opusModel, sonnetModel, haikuModel, target } = req.body;
    const providerHint = req.body?.droidProvider ?? req.body?.provider;
    const parsedProvider = (0, droid_provider_1.normalizeDroidProvider)(providerHint);
    const normalizedBaseUrl = typeof baseUrl === 'string' ? baseUrl.trim() : baseUrl;
    const normalizedApiKey = typeof apiKey === 'string' ? apiKey.trim() : apiKey;
    const parsedTarget = (0, route_helpers_1.parseTarget)(target);
    if (target !== undefined && parsedTarget === null) {
        res.status(400).json({ error: `Invalid target. Expected: ${(0, target_metadata_1.getPersistedTargetChoices)()}` });
        return;
    }
    if (providerHint !== undefined && parsedProvider === null) {
        res.status(400).json({
            error: 'Invalid droid provider. Expected: anthropic, openai, or generic-chat-completion-api',
        });
        return;
    }
    // Check if profile exists (uses unified config when available)
    if (!(0, services_1.apiProfileExists)(name)) {
        res.status(404).json({ error: 'Profile not found' });
        return;
    }
    // Validate required fields if provided (prevent setting to empty)
    if (normalizedApiKey !== undefined && !normalizedApiKey) {
        res.status(400).json({ error: 'apiKey cannot be empty' });
        return;
    }
    try {
        const hasSettingsUpdates = baseUrl !== undefined ||
            apiKey !== undefined ||
            model !== undefined ||
            extraModels !== undefined ||
            opusModel !== undefined ||
            sonnetModel !== undefined ||
            haikuModel !== undefined ||
            providerHint !== undefined;
        const hasTargetUpdate = target !== undefined;
        if (!hasSettingsUpdates && !hasTargetUpdate) {
            res.status(400).json({ error: 'No updates provided' });
            return;
        }
        if (hasSettingsUpdates) {
            (0, route_helpers_1.updateSettingsFile)(name, {
                baseUrl: normalizedBaseUrl,
                apiKey: normalizedApiKey,
                model,
                extraModels,
                opusModel,
                sonnetModel,
                haikuModel,
                provider: parsedProvider || undefined,
            });
        }
        if (hasTargetUpdate && parsedTarget) {
            const targetUpdate = (0, services_1.updateApiProfileTarget)(name, parsedTarget);
            if (!targetUpdate.success) {
                res.status(500).json({ error: targetUpdate.error || 'Failed to update target' });
                return;
            }
        }
        res.json({
            name,
            updated: true,
            ...(hasTargetUpdate && parsedTarget && { target: parsedTarget }),
        });
    }
    catch (error) {
        const errorMessage = error.message;
        res.status(isDenylistError(errorMessage) ? 400 : 500).json({ error: errorMessage });
    }
});
/**
 * DELETE /api/profiles/:name - Delete profile
 */
router.delete('/:name', (req, res) => {
    const { name } = req.params;
    // Check if profile exists (uses unified config when available)
    if (!(0, services_1.apiProfileExists)(name)) {
        res.status(404).json({ error: 'Profile not found' });
        return;
    }
    // Remove profile using unified-config-aware service
    const result = (0, services_1.removeApiProfile)(name);
    if (!result.success) {
        res.status(500).json({ error: result.error || 'Failed to delete profile' });
        return;
    }
    res.json({ name, deleted: true });
});
exports.default = router;
var route_helpers_2 = require("./route-helpers");
Object.defineProperty(exports, "parseTarget", { enumerable: true, get: function () { return route_helpers_2.parseTarget; } });
//# sourceMappingURL=profile-routes.js.map