"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 });
exports.getResolvedCatalogSnapshot = exports.getAllResolvedCatalogs = exports.getResolvedCatalog = exports.mergeCatalog = exports.refreshCatalogFromProxy = exports.getCacheAge = exports.clearCatalogCache = exports.setCachedCatalog = exports.getCachedCatalog = exports.SYNCABLE_PROVIDERS = exports.PROVIDER_TO_CHANNEL = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const model_catalog_1 = require("../model-catalog");
const model_id_normalizer_1 = require("../ai-providers/model-id-normalizer");
const proxy_target_resolver_1 = require("../proxy/proxy-target-resolver");
const config_loader_facade_1 = require("../../config/config-loader-facade");
const CACHE_FILE_NAME = 'model-catalog-cache.json';
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
const LIVE_FETCH_TIMEOUT_MS = 3000;
/** Channel name → CCS provider mapping */
const CHANNEL_TO_PROVIDER = {
    antigravity: 'agy',
    claude: 'claude',
    gemini: 'gemini',
    codex: 'codex',
    qwen: 'qwen',
    iflow: 'iflow',
    kimi: 'kimi',
    kiro: 'kiro',
    'github-copilot': 'ghcp',
};
/** CCS provider → channel name mapping (reverse) */
exports.PROVIDER_TO_CHANNEL = Object.fromEntries(Object.entries(CHANNEL_TO_PROVIDER).map(([k, v]) => [v, k]));
/** Providers to sync from CLIProxyAPI */
exports.SYNCABLE_PROVIDERS = [
    ...new Set(Object.values(CHANNEL_TO_PROVIDER)),
];
function getCacheFilePath() {
    return path.join((0, config_loader_facade_1.getCcsDir)(), CACHE_FILE_NAME);
}
/** Read cached catalog data, null if expired or missing */
function getCachedCatalog() {
    try {
        const filePath = getCacheFilePath();
        if (!fs.existsSync(filePath))
            return null;
        const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
        if (Date.now() - data.fetchedAt > CACHE_TTL_MS)
            return null;
        return data;
    }
    catch {
        return null;
    }
}
exports.getCachedCatalog = getCachedCatalog;
/** Save catalog data to cache */
function setCachedCatalog(providers) {
    try {
        const filePath = getCacheFilePath();
        const dir = path.dirname(filePath);
        if (!fs.existsSync(dir))
            fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(filePath, JSON.stringify({ providers, fetchedAt: Date.now() }));
    }
    catch {
        // Ignore cache write errors
    }
}
exports.setCachedCatalog = setCachedCatalog;
/** Delete cache file */
function clearCatalogCache() {
    try {
        const filePath = getCacheFilePath();
        if (fs.existsSync(filePath)) {
            fs.unlinkSync(filePath);
            return true;
        }
        return false;
    }
    catch {
        return false;
    }
}
exports.clearCatalogCache = clearCatalogCache;
/** Get cache age in human-readable format, or null if no cache */
function getCacheAge() {
    try {
        const filePath = getCacheFilePath();
        if (!fs.existsSync(filePath))
            return null;
        const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
        const ageMs = Date.now() - data.fetchedAt;
        const hours = Math.floor(ageMs / (60 * 60 * 1000));
        const minutes = Math.floor((ageMs % (60 * 60 * 1000)) / (60 * 1000));
        if (hours > 0)
            return `${hours}h ${minutes}m ago`;
        return `${minutes}m ago`;
    }
    catch {
        return null;
    }
}
exports.getCacheAge = getCacheAge;
async function fetchProviderCatalog(provider) {
    const channel = exports.PROVIDER_TO_CHANNEL[provider];
    if (!channel) {
        return [provider, null];
    }
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), LIVE_FETCH_TIMEOUT_MS);
    try {
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        const response = await fetch((0, proxy_target_resolver_1.buildProxyUrl)(target, `/v0/management/model-definitions/${channel}`), {
            signal: controller.signal,
            headers: (0, proxy_target_resolver_1.buildManagementHeaders)(target),
        });
        if (!response.ok) {
            return [provider, null];
        }
        const data = (await response.json());
        return [provider, Array.isArray(data.models) ? data.models : null];
    }
    catch {
        return [provider, null];
    }
    finally {
        clearTimeout(timeoutId);
    }
}
async function isProxyCatalogReachable() {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 1000);
    try {
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        const response = await fetch((0, proxy_target_resolver_1.buildProxyUrl)(target, '/'), {
            signal: controller.signal,
        });
        return response.ok;
    }
    catch {
        return false;
    }
    finally {
        clearTimeout(timeoutId);
    }
}
async function refreshCatalogFromProxy() {
    if (!(await isProxyCatalogReachable())) {
        return null;
    }
    const settled = await Promise.all(exports.SYNCABLE_PROVIDERS.map((provider) => fetchProviderCatalog(provider)));
    const providers = Object.fromEntries(settled.filter(([, models]) => Array.isArray(models) && models.length > 0));
    if (Object.keys(providers).length === 0) {
        return null;
    }
    setCachedCatalog(providers);
    return providers;
}
exports.refreshCatalogFromProxy = refreshCatalogFromProxy;
/** Map remote thinking support to CCS ThinkingSupport */
function mapThinking(remote) {
    if (!remote)
        return undefined;
    // If levels are provided, it's a levels-type thinking
    if (remote.levels && remote.levels.length > 0) {
        return {
            type: 'levels',
            levels: remote.levels,
            dynamicAllowed: remote.dynamic_allowed,
        };
    }
    // If min/max budget are provided, it's budget-type
    if (remote.min !== undefined || remote.max !== undefined) {
        return {
            type: 'budget',
            min: remote.min,
            max: remote.max,
            zeroAllowed: remote.zero_allowed,
            dynamicAllowed: remote.dynamic_allowed,
        };
    }
    return { type: 'none' };
}
/** Map RemoteModelInfo to ModelEntry */
function mapRemoteToModelEntry(remote) {
    const entry = {
        id: remote.id,
        name: remote.display_name || remote.id,
    };
    if (remote.description)
        entry.description = remote.description;
    if (remote.context_length && remote.context_length >= 1000000) {
        entry.extendedContext = true;
    }
    const thinking = mapThinking(remote.thinking);
    if (thinking)
        entry.thinking = thinking;
    return entry;
}
/**
 * Merge remote models with static catalog for a provider.
 * Remote fields override static where present.
 * Static-only fields preserved: broken, deprecated, deprecationReason, issueUrl, tier.
 * Models in remote but not in static → added.
 * Models removed upstream stay hidden; UI falls back to static only when live data is unavailable.
 */
function mergeCatalog(provider, remoteModels) {
    const filteredRemoteModels = provider === 'agy'
        ? remoteModels.filter((remoteModel) => !(0, model_id_normalizer_1.getDeniedModelIdReasonForProvider)(remoteModel.id, provider))
        : remoteModels;
    const staticCatalog = model_catalog_1.MODEL_CATALOG[provider];
    if (!staticCatalog && filteredRemoteModels.length === 0)
        return undefined;
    const displayName = staticCatalog?.displayName || provider;
    const defaultModel = staticCatalog?.defaultModel || (filteredRemoteModels[0]?.id ?? '');
    // Build map of static models by lowercase ID for fast lookup
    const staticMap = new Map();
    if (staticCatalog) {
        for (const model of staticCatalog.models) {
            staticMap.set(model.id.toLowerCase(), model);
        }
    }
    // Process remote models: merge with static entries
    const mergedIds = new Set();
    const mergedModels = [];
    for (const remote of filteredRemoteModels) {
        const remoteEntry = mapRemoteToModelEntry(remote);
        const staticEntry = staticMap.get(remote.id.toLowerCase());
        mergedIds.add(remote.id.toLowerCase());
        if (staticEntry) {
            const mergedThinking = remoteEntry.thinking
                ? {
                    ...remoteEntry.thinking,
                    maxLevel: remoteEntry.thinking.maxLevel ?? staticEntry.thinking?.maxLevel,
                }
                : staticEntry.thinking;
            // Merge: remote overrides, static fills gaps
            mergedModels.push({
                ...remoteEntry,
                thinking: mergedThinking,
                // Preserve static-only fields
                tier: staticEntry.tier,
                broken: staticEntry.broken,
                issueUrl: staticEntry.issueUrl,
                deprecated: staticEntry.deprecated,
                deprecationReason: staticEntry.deprecationReason,
            });
        }
        else {
            mergedModels.push(remoteEntry);
        }
    }
    return {
        provider,
        displayName,
        defaultModel,
        models: mergedModels,
    };
}
exports.mergeCatalog = mergeCatalog;
function getResolvedCatalogFromProviders(provider, providers) {
    if (providers?.[provider]) {
        return mergeCatalog(provider, providers[provider]);
    }
    return model_catalog_1.MODEL_CATALOG[provider];
}
function getAllResolvedCatalogsFromProviders(providers) {
    const result = {};
    const providerIds = new Set();
    for (const provider of Object.keys(model_catalog_1.MODEL_CATALOG)) {
        providerIds.add(provider);
    }
    if (providers) {
        for (const provider of Object.keys(providers)) {
            providerIds.add(provider);
        }
    }
    for (const provider of providerIds) {
        const catalog = getResolvedCatalogFromProviders(provider, providers);
        if (catalog) {
            result[provider] = catalog;
        }
    }
    return result;
}
/**
 * Get resolved catalog for a provider.
 * Uses cached remote data if available, falls back to static.
 */
function getResolvedCatalog(provider) {
    const cached = getCachedCatalog();
    if (cached && cached.providers[provider]) {
        return mergeCatalog(provider, cached.providers[provider]);
    }
    return model_catalog_1.MODEL_CATALOG[provider];
}
exports.getResolvedCatalog = getResolvedCatalog;
/**
 * Get all resolved catalogs (for Dashboard).
 */
function getAllResolvedCatalogs() {
    const cached = getCachedCatalog();
    return getAllResolvedCatalogsFromProviders(cached?.providers);
}
exports.getAllResolvedCatalogs = getAllResolvedCatalogs;
async function getResolvedCatalogSnapshot() {
    const liveProviders = await refreshCatalogFromProxy();
    if (liveProviders) {
        return {
            catalogs: getAllResolvedCatalogsFromProviders(liveProviders),
            source: 'live',
            cacheAge: getCacheAge(),
        };
    }
    const cached = getCachedCatalog();
    if (cached?.providers) {
        return {
            catalogs: getAllResolvedCatalogsFromProviders(cached.providers),
            source: 'cache',
            cacheAge: getCacheAge(),
        };
    }
    return {
        catalogs: getAllResolvedCatalogsFromProviders(),
        source: 'static',
        cacheAge: null,
    };
}
exports.getResolvedCatalogSnapshot = getResolvedCatalogSnapshot;
//# sourceMappingURL=catalog-cache.js.map