import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { logger } from '../utils/logger.js';
import { summarizeError } from '../utils/error-display.js';
const ZEROEVAL_URL = 'https://api.zeroeval.com/leaderboard/models/full?justCanonicals=true';
const FETCH_TIMEOUT_MS = 20_000;
const CACHE_TTL_MS = 86_400_000;
export const S_TIER_THRESHOLD = 0.80;
export const A_TIER_THRESHOLD = 0.65;
export const B_TIER_THRESHOLD = 0.50;
function pickFirst(...values) {
    for (const value of values) {
        if (value != null)
            return value;
    }
    return undefined;
}
function parseScore(value) {
    if (value == null || Number.isNaN(value))
        return undefined;
    return value > 1 ? value / 100 : value;
}
function extractBenchmarks(model) {
    const scores = model.scores ?? {};
    const raw = {
        gpqa: pickFirst(model.gpqa_score, model.gpqa_diamond, model.gpqa, scores.gpqa_score, scores.gpqa_diamond, scores.gpqa),
        swe: pickFirst(model.swe_bench_verified_score, model.swe_bench, model.swe, scores.swe_bench_verified_score, scores.swe_bench, scores.swe),
        aime: pickFirst(model.aime_2025_score, model.aime_2024, model.aime, scores.aime_2025_score, scores.aime_2024, scores.aime),
        terminal: pickFirst(model.terminal_bench_score, model.terminal_bench, model.terminal, scores.terminal_bench_score, scores.terminal_bench, scores.terminal),
        tool: pickFirst(model.toolathlon_score, model.tool_use, model.tool, scores.toolathlon_score, scores.tool_use, scores.tool),
        mcp: pickFirst(model.mcp_atlas_score, model.mcp_bench, model.mcp, scores.mcp_atlas_score, scores.mcp_bench, scores.mcp),
    };
    const benchmarks = {};
    if (raw.gpqa != null)
        benchmarks.gpqa = parseScore(raw.gpqa);
    if (raw.swe != null)
        benchmarks.swe = parseScore(raw.swe);
    if (raw.aime != null)
        benchmarks.aime = parseScore(raw.aime);
    if (raw.terminal != null)
        benchmarks.terminal = parseScore(raw.terminal);
    if (raw.tool != null)
        benchmarks.tool = parseScore(raw.tool);
    if (raw.mcp != null)
        benchmarks.mcp = parseScore(raw.mcp);
    return benchmarks;
}
function parseEntries(json) {
    const raw = Array.isArray(json)
        ? json
        : (json.models ?? json.data ?? json.leaderboard ?? []);
    if (!Array.isArray(raw)) {
        logger.warn('[model-benchmarks] Unexpected ZeroEval response shape');
        return [];
    }
    return raw.map((model) => ({
        modelId: String(model.id ?? model.model_id ?? ''),
        name: String(model.name ?? model.model_name ?? model.id ?? model.model_id ?? ''),
        organization: String(model.organization ?? model.org ?? ''),
        benchmarks: extractBenchmarks(model),
    }));
}
function buildNameIndex(entries) {
    const index = new Map();
    for (const entry of entries) {
        index.set(entry.name.toLowerCase(), entry);
        if (entry.modelId) {
            index.set(entry.modelId.toLowerCase(), entry);
        }
    }
    return index;
}
export function compositeScore(benchmarks) {
    let total = 0;
    let weight = 0;
    if (benchmarks.swe != null) {
        total += benchmarks.swe * 0.4;
        weight += 0.4;
    }
    if (benchmarks.gpqa != null) {
        total += benchmarks.gpqa * 0.4;
        weight += 0.4;
    }
    if (benchmarks.aime != null) {
        total += benchmarks.aime * 0.2;
        weight += 0.2;
    }
    return weight === 0 ? null : total / weight;
}
export function getQualityTier(benchmarks) {
    const score = compositeScore(benchmarks);
    if (score == null)
        return 'C';
    return getQualityTierFromScore(score);
}
export function getQualityTierFromScore(score) {
    if (score >= S_TIER_THRESHOLD)
        return 'S';
    if (score >= A_TIER_THRESHOLD)
        return 'A';
    if (score >= B_TIER_THRESHOLD)
        return 'B';
    return 'C';
}
export class BenchmarkStore {
    dir;
    cache = null;
    nameIndex = null;
    refreshCallbacks = new Set();
    constructor(options) {
        this.dir = options.dir;
    }
    getCachePath() {
        return join(this.dir, 'benchmarks.json');
    }
    getTmpPath() {
        return `${this.getCachePath()}.tmp`;
    }
    onRefreshed(callback) {
        this.refreshCallbacks.add(callback);
        return () => {
            this.refreshCallbacks.delete(callback);
        };
    }
    initBenchmarks() {
        this.cache = this.loadCache();
        this.nameIndex = this.cache ? buildNameIndex(this.cache.entries) : null;
        if (!this.cache || this.isCacheStale(this.cache)) {
            void this.refreshBenchmarks().catch((err) => {
                logger.debug('[model-benchmarks] Background refresh failed', { error: summarizeError(err) });
            });
        }
    }
    async refreshBenchmarks() {
        const entries = await this.fetchBenchmarks();
        if (entries.length === 0) {
            logger.warn('[model-benchmarks] Refresh returned 0 entries — keeping existing cache');
            return;
        }
        const next = {
            version: 1,
            fetchedAt: Date.now(),
            ttlMs: CACHE_TTL_MS,
            entries,
        };
        this.saveCache(next);
        this.cache = next;
        this.nameIndex = buildNameIndex(entries);
        for (const callback of this.refreshCallbacks)
            callback();
        logger.debug('[model-benchmarks] Cache updated', { count: entries.length });
    }
    getBenchmarks(modelName) {
        const entries = this.cache?.entries;
        if (!entries || entries.length === 0)
            return undefined;
        const index = this.nameIndex ?? buildNameIndex(entries);
        const exact = entries.find((entry) => entry.name === modelName || entry.modelId === modelName);
        if (exact)
            return exact;
        const lower = modelName.toLowerCase();
        const indexed = index.get(lower);
        if (indexed)
            return indexed;
        const slug = lower.replace(/[^a-z0-9]/g, '');
        if (slug.length > 0) {
            let slugBest;
            let slugBestLen = Infinity;
            let slugPrefixBest;
            let slugPrefixBestLen = Infinity;
            for (const entry of entries) {
                const nameSlug = entry.name.toLowerCase().replace(/[^a-z0-9]/g, '');
                const idSlug = entry.modelId.toLowerCase().replace(/[^a-z0-9]/g, '');
                if (nameSlug === slug || idSlug === slug) {
                    const len = Math.min(nameSlug === slug ? entry.name.length : Infinity, idSlug === slug ? entry.modelId.length : Infinity);
                    if (len < slugBestLen) {
                        slugBestLen = len;
                        slugBest = entry;
                    }
                }
                else if (nameSlug.startsWith(slug) || idSlug.startsWith(slug)) {
                    const len = Math.min(nameSlug.startsWith(slug) ? entry.name.length : Infinity, idSlug.startsWith(slug) ? entry.modelId.length : Infinity);
                    if (len < slugPrefixBestLen) {
                        slugPrefixBestLen = len;
                        slugPrefixBest = entry;
                    }
                }
            }
            if (slugBest)
                return slugBest;
            if (slugPrefixBest)
                return slugPrefixBest;
        }
        let best;
        let bestLen = Infinity;
        for (const entry of entries) {
            const nameLower = entry.name.toLowerCase();
            const idLower = entry.modelId.toLowerCase();
            if (nameLower.includes(lower) || idLower.includes(lower)) {
                const len = Math.min(nameLower.includes(lower) ? entry.name.length : Infinity, idLower.includes(lower) ? entry.modelId.length : Infinity);
                if (len < bestLen) {
                    bestLen = len;
                    best = entry;
                }
            }
        }
        return best;
    }
    getTopBenchmarkModelIds(n) {
        const entries = this.cache?.entries;
        if (!entries || entries.length === 0)
            return [];
        return entries
            .map((entry) => ({ id: entry.modelId, score: compositeScore(entry.benchmarks) }))
            .filter((entry) => entry.score != null)
            .sort((a, b) => b.score - a.score)
            .slice(0, n)
            .map((entry) => entry.id);
    }
    async fetchBenchmarks() {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
        try {
            const response = await fetch(ZEROEVAL_URL, {
                signal: controller.signal,
                headers: { Accept: 'application/json' },
            });
            if (!response.ok) {
                throw new Error(`ZeroEval API returned ${response.status} ${response.statusText}`);
            }
            return parseEntries(await response.json());
        }
        finally {
            clearTimeout(timer);
        }
    }
    loadCache() {
        try {
            const parsed = JSON.parse(readFileSync(this.getCachePath(), 'utf-8'));
            if (parsed.version !== 1 || !Array.isArray(parsed.entries))
                return null;
            return parsed;
        }
        catch (err) {
            const message = summarizeError(err);
            if (message.includes('ENOENT') || message.includes('no such file')) {
                logger.debug('[model-benchmarks] No cache file found (first run)');
            }
            else {
                logger.warn('[model-benchmarks] Cache load failed (corrupted?)', { error: message });
            }
            return null;
        }
    }
    saveCache(cache) {
        try {
            mkdirSync(this.dir, { recursive: true });
            writeFileSync(this.getTmpPath(), JSON.stringify(cache, null, 2), 'utf-8');
            renameSync(this.getTmpPath(), this.getCachePath());
        }
        catch (err) {
            logger.warn('[model-benchmarks] Cache write failed', { error: summarizeError(err) });
        }
    }
    isCacheStale(cache) {
        return Date.now() - cache.fetchedAt > cache.ttlMs;
    }
}
