"use strict";
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 provider_capabilities_1 = require("../../cliproxy/provider-capabilities");
const services_1 = require("../../api/services");
const auth_middleware_1 = require("../middleware/auth-middleware");
const helpers_1 = require("../../utils/helpers");
const model_id_normalizer_1 = require("../../cliproxy/ai-providers/model-id-normalizer");
const hooks_1 = require("../../utils/hooks");
const image_analyzer_hook_installer_1 = require("../../utils/hooks/image-analyzer-hook-installer");
const image_analyzer_profile_hook_injector_1 = require("../../utils/hooks/image-analyzer-profile-hook-injector");
const image_analysis_1 = require("../../utils/image-analysis");
const config_loader_facade_1 = require("../../config/config-loader-facade");
const router = (0, express_1.Router)();
const IMAGE_ANALYSIS_LOCAL_ACCESS_ERROR = 'Image Analysis endpoints require localhost access when dashboard auth is disabled.';
function safeLoadSettings(settingsPath) {
    if (!settingsPath)
        return null;
    try {
        const expandedPath = (0, helpers_1.expandPath)(settingsPath);
        if (!fs.existsSync(expandedPath))
            return null;
        return (0, config_loader_facade_1.loadSettings)(expandedPath);
    }
    catch {
        return null;
    }
}
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 resolveTarget(target) {
    if (target === 'droid' || target === 'codex')
        return target;
    return 'claude';
}
function resolveCurrentTargetMode(target, status, managedToolReady) {
    if (!status.enabled)
        return 'disabled';
    if (target !== 'claude')
        return 'bypassed';
    if (status.nativeReadPreference)
        return 'native';
    if (!managedToolReady)
        return 'setup';
    if (!status.backendId)
        return 'unresolved';
    if (status.effectiveRuntimeMode === 'native-read')
        return 'fallback';
    return 'active';
}
function resolveBackendState(status) {
    if (status.authReadiness === 'missing')
        return 'needs_auth';
    if (status.proxyReadiness === 'unavailable')
        return 'needs_proxy';
    if (status.proxyReadiness === 'stopped')
        return 'starts_on_launch';
    if (status.effectiveRuntimeMode === 'native-read' || status.status === 'attention')
        return 'review';
    return 'ready';
}
function getKnownBackends() {
    return Array.from(new Set(provider_capabilities_1.CLIPROXY_PROVIDER_IDS)).sort((left, right) => left.localeCompare(right));
}
async function buildDashboardPayload() {
    const config = (0, config_loader_facade_1.getImageAnalysisConfig)();
    const { profiles, variants } = (0, services_1.listApiProfiles)();
    const sharedHookInstalled = (0, image_analyzer_hook_installer_1.hasImageAnalyzerHook)();
    const managedToolReady = (0, image_analysis_1.hasImageAnalysisMcpReady)();
    const profileRows = await Promise.all(profiles.map(async (profile) => {
        const settingsPath = profile.settingsPath || null;
        const settings = safeLoadSettings(settingsPath);
        const cliproxyProvider = (0, provider_capabilities_1.mapExternalProviderName)(profile.name) ??
            (0, services_1.resolveCliproxyBridgeMetadata)(settings ?? undefined)?.provider ??
            resolveProviderFromBaseUrl(settings?.env?.ANTHROPIC_BASE_URL);
        const status = await (0, hooks_1.resolveImageAnalysisRuntimeStatus)({
            profileName: profile.name,
            profileType: 'settings',
            cliproxyProvider,
            settingsPath,
            settings,
            cliproxyBridge: (0, services_1.resolveCliproxyBridgeMetadata)(settings ?? undefined),
            hookInstalled: settingsPath
                ? (0, image_analyzer_profile_hook_injector_1.hasImageAnalysisProfileHook)(profile.name, settingsPath)
                : undefined,
            sharedHookInstalled,
        }, config);
        return {
            name: profile.name,
            kind: 'profile',
            target: resolveTarget(profile.target),
            configured: profile.isConfigured,
            settingsPath,
            backendId: status.backendId,
            backendDisplayName: status.backendDisplayName,
            resolutionSource: status.resolutionSource,
            status: status.status,
            effectiveRuntimeMode: status.effectiveRuntimeMode,
            effectiveRuntimeReason: status.effectiveRuntimeReason,
            currentTargetMode: resolveCurrentTargetMode(resolveTarget(profile.target), status, managedToolReady),
            profileModel: status.profileModel,
            nativeReadPreference: status.nativeReadPreference,
            nativeImageCapable: status.nativeImageCapable,
            nativeImageReason: status.nativeImageReason,
        };
    }));
    const variantRows = await Promise.all(variants.map(async (variant) => {
        const settingsPath = typeof variant.settings === 'string' && variant.settings !== '-' ? variant.settings : null;
        const settings = safeLoadSettings(settingsPath);
        const cliproxyProvider = (0, provider_capabilities_1.mapExternalProviderName)(variant.provider);
        const status = await (0, hooks_1.resolveImageAnalysisRuntimeStatus)({
            profileName: variant.name,
            profileType: 'cliproxy',
            cliproxyProvider,
            isComposite: variant.provider === 'composite',
            settingsPath,
            settings,
            cliproxyBridge: (0, services_1.resolveCliproxyBridgeMetadata)(settings ?? undefined),
            hookInstalled: settingsPath
                ? (0, image_analyzer_profile_hook_injector_1.hasImageAnalysisProfileHook)(variant.name, settingsPath)
                : undefined,
            sharedHookInstalled,
        }, config);
        return {
            name: variant.name,
            kind: 'variant',
            target: resolveTarget(variant.target),
            configured: true,
            settingsPath,
            backendId: status.backendId,
            backendDisplayName: status.backendDisplayName,
            resolutionSource: status.resolutionSource,
            status: status.status,
            effectiveRuntimeMode: status.effectiveRuntimeMode,
            effectiveRuntimeReason: status.effectiveRuntimeReason,
            currentTargetMode: resolveCurrentTargetMode(resolveTarget(variant.target), status, managedToolReady),
            profileModel: status.profileModel,
            nativeReadPreference: status.nativeReadPreference,
            nativeImageCapable: status.nativeImageCapable,
            nativeImageReason: status.nativeImageReason,
        };
    }));
    const allProfileRows = [...profileRows, ...variantRows].sort((left, right) => left.name.localeCompare(right.name));
    const backendRows = await Promise.all(Object.entries(config.provider_models)
        .sort(([left], [right]) => left.localeCompare(right))
        .map(async ([backendId, model]) => {
        const status = await (0, hooks_1.resolveImageAnalysisRuntimeStatus)({
            profileName: backendId,
            profileType: 'cliproxy',
            cliproxyProvider: (0, provider_capabilities_1.mapExternalProviderName)(backendId),
            hookInstalled: true,
            sharedHookInstalled: true,
        }, config);
        return {
            backendId,
            displayName: (0, provider_capabilities_1.getProviderDisplayName)(backendId),
            model,
            state: resolveBackendState(status),
            authReadiness: status.authReadiness,
            authReason: status.authReason,
            proxyReadiness: status.proxyReadiness,
            proxyReason: status.proxyReason,
            profilesUsing: allProfileRows.filter((profile) => profile.backendId === backendId && !profile.nativeReadPreference).length,
        };
    }));
    const activeProfileCount = allProfileRows.filter((row) => row.currentTargetMode === 'active').length;
    const bypassedProfileCount = allProfileRows.filter((row) => row.currentTargetMode === 'bypassed').length;
    const mappedProfileCount = allProfileRows.filter((row) => row.resolutionSource === 'profile-backend').length;
    const nativeProfileCount = allProfileRows.filter((row) => row.nativeReadPreference).length;
    const blockerCount = backendRows.filter((row) => row.state === 'needs_auth' || row.state === 'needs_proxy' || row.state === 'review').length;
    let summaryState = 'ready';
    let title = 'Ready';
    let detail = `${activeProfileCount} profile${activeProfileCount === 1 ? '' : 's'} route through Image on the current Claude target path.`;
    if (nativeProfileCount > 0) {
        detail += ` ${nativeProfileCount} prefer native image reading.`;
    }
    if (!config.enabled) {
        summaryState = 'disabled';
        title = 'Disabled';
        detail = 'Image is turned off globally. Images and PDFs fall back to native file access.';
    }
    else if (!managedToolReady) {
        summaryState = 'needs_setup';
        title = 'Needs local runtime';
        detail =
            'CCS could not provision the local ImageAnalysis MCP runtime yet. Profiles will fall back to native Read until provisioning succeeds.';
    }
    else if (backendRows.length === 0) {
        summaryState = 'needs_setup';
        title = 'Needs provider models';
        detail = 'Add at least one provider model before turning Image on for profiles.';
    }
    else if (blockerCount > 0) {
        summaryState = activeProfileCount > 0 ? 'partial' : 'needs_setup';
        title = activeProfileCount > 0 ? 'Partially ready' : 'Needs setup';
        detail = `${blockerCount} backend${blockerCount === 1 ? '' : 's'} still need auth, runtime, or review before every profile path is healthy.`;
    }
    return {
        config: {
            enabled: config.enabled,
            timeout: config.timeout,
            providerModels: config.provider_models,
            fallbackBackend: config.fallback_backend ?? null,
            profileBackends: config.profile_backends ?? {},
        },
        summary: {
            state: summaryState,
            title,
            detail,
            backendCount: backendRows.length,
            mappedProfileCount,
            activeProfileCount,
            bypassedProfileCount,
            nativeProfileCount,
        },
        runtime: {
            managedToolReady,
            sharedHookInstalled,
        },
        backends: backendRows,
        profiles: allProfileRows,
        catalog: {
            knownBackends: getKnownBackends(),
            profileNames: allProfileRows.map((row) => row.name),
        },
    };
}
router.use((req, res, next) => {
    if ((0, auth_middleware_1.requireLocalAccessWhenAuthDisabled)(req, res, IMAGE_ANALYSIS_LOCAL_ACCESS_ERROR)) {
        next();
    }
});
router.get('/', async (_req, res) => {
    try {
        res.json(await buildDashboardPayload());
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
router.put('/', async (req, res) => {
    const body = req.body;
    if (!body || typeof body !== 'object' || Array.isArray(body)) {
        res.status(400).json({ error: 'Invalid request body. Must be an object.' });
        return;
    }
    if (body.enabled !== undefined && typeof body.enabled !== 'boolean') {
        res.status(400).json({ error: 'Invalid value for enabled. Must be a boolean.' });
        return;
    }
    if (body.timeout !== undefined) {
        if (!Number.isInteger(body.timeout) || body.timeout < 10 || body.timeout > 600) {
            res.status(400).json({ error: 'Timeout must be an integer between 10 and 600 seconds.' });
            return;
        }
    }
    if (body.providerModels !== undefined &&
        (body.providerModels === null ||
            Array.isArray(body.providerModels) ||
            typeof body.providerModels !== 'object')) {
        res.status(400).json({ error: 'Invalid value for providerModels. Must be an object.' });
        return;
    }
    if (body.profileBackends !== undefined &&
        (body.profileBackends === null ||
            Array.isArray(body.profileBackends) ||
            typeof body.profileBackends !== 'object')) {
        res.status(400).json({ error: 'Invalid value for profileBackends. Must be an object.' });
        return;
    }
    if (body.fallbackBackend !== undefined &&
        body.fallbackBackend !== null &&
        typeof body.fallbackBackend !== 'string') {
        res.status(400).json({ error: 'Invalid value for fallbackBackend. Must be a string or null.' });
        return;
    }
    try {
        const currentConfig = (0, config_loader_facade_1.getImageAnalysisConfig)();
        const knownBackends = new Set([
            ...getKnownBackends(),
            ...Object.keys(currentConfig.provider_models),
        ]);
        const nextProviderModels = Object.entries(body.providerModels ?? currentConfig.provider_models).reduce((acc, [backendId, model]) => {
            const normalizedBackend = (0, hooks_1.normalizeImageAnalysisBackendId)(backendId, knownBackends);
            const normalizedModel = typeof model === 'string' ? model.trim() : '';
            if (!normalizedBackend || normalizedModel.length === 0) {
                return acc;
            }
            acc[normalizedBackend] = normalizedModel;
            return acc;
        }, {});
        if (Object.keys(nextProviderModels).length === 0) {
            res.status(400).json({ error: 'At least one provider model must remain configured.' });
            return;
        }
        const requestedFallback = typeof body.fallbackBackend === 'string'
            ? body.fallbackBackend
            : currentConfig.fallback_backend;
        const normalizedFallback = (0, hooks_1.normalizeImageAnalysisBackendId)(requestedFallback, Object.keys(nextProviderModels));
        if (!normalizedFallback || !nextProviderModels[normalizedFallback]) {
            res
                .status(400)
                .json({ error: 'Fallback backend must reference a configured provider model.' });
            return;
        }
        const nextProfileBackends = {};
        for (const [profileName, backendId] of Object.entries(body.profileBackends ?? currentConfig.profile_backends ?? {})) {
            const trimmedProfileName = profileName.trim();
            if (!trimmedProfileName) {
                continue;
            }
            const normalizedBackend = (0, hooks_1.normalizeImageAnalysisBackendId)(backendId, Object.keys(nextProviderModels));
            if (!normalizedBackend || !nextProviderModels[normalizedBackend]) {
                res.status(400).json({
                    error: `Profile mapping for "${trimmedProfileName}" references an unknown backend.`,
                });
                return;
            }
            nextProfileBackends[trimmedProfileName] = normalizedBackend;
        }
        (0, config_loader_facade_1.mutateConfig)((config) => {
            config.image_analysis = {
                enabled: body.enabled ?? currentConfig.enabled,
                timeout: body.timeout ?? currentConfig.timeout,
                provider_models: nextProviderModels,
                fallback_backend: normalizedFallback,
                profile_backends: nextProfileBackends,
            };
        });
        const nextEnabled = body.enabled ?? currentConfig.enabled;
        if (nextEnabled) {
            (0, image_analysis_1.repairImageAnalysisRuntimeState)();
        }
        res.json(await buildDashboardPayload());
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
exports.default = router;
//# sourceMappingURL=image-analysis-routes.js.map