/**
 * Proactive prefetch for live-world questions.
 *
 * Why this exists:
 * When a user asks "what is CRCL trading at?", the agent has TradingMarket
 * in CORE and the system prompt demands it be used. The evaluator catches
 * refusals. The auto-retry loop feeds findings back. All four layers run
 * every turn. It still isn't enough — Sonnet 4.6 (the strongest model we
 * route to) confidently answers "Circle is a private company" from 2022
 * training data, refusing the tool across retries.
 *
 * The lesson: every mechanism above depends on the model *agreeing* to call
 * a tool. When the model is confident-but-wrong about current-world state,
 * it doesn't reach for the tool at all. No prompt tweak will fix this —
 * fine-tuning priors beat prompt priors.
 *
 * Harness-level fix: prefetch the data *before* the model decides. When
 * the user's message contains a ticker or a current-events ask, Franklin's
 * harness spends the $0.001 unprompted, injects the result into context,
 * and then the model answers a question it already has evidence for —
 * not a question its training data has a prior about.
 *
 * This is the pattern Anthropic's harness-design writeup calls out:
 * "Remove components that encode a stale assumption (the model will
 * reach for tools on its own), replace with components that handle the
 * coordination gap (harness fetches, model synthesizes)."
 */
import { getStockPrice, getPrice } from '../trading/data.js';
import { blockrunSpendUsdToday } from '../trading/providers/telemetry.js';
// ─── Intent source ──────────────────────────────────────────────────────
//
// Historical note: this file used to host its own LLM classifier
// (`classifyIntent` + `parseIntentReply` + a ~40-line STOCK/CRYPTO/NONE
// prompt). Since v3.8.27 the unified `turn-analyzer.ts` produces intent
// as part of a single pre-turn call, and `loop.ts` reads
// `turnAnalysis.intent` directly — the standalone classifier was dead
// code with no remaining callers. Removed in v3.8.29. The TurnIntent
// shape lives in turn-analyzer and is consumed by `prefetchForIntent`
// below.
// ─── Prefetch dispatcher ─────────────────────────────────────────────────
function formatUsd(n) {
    if (!Number.isFinite(n))
        return '—';
    if (n >= 100)
        return `$${n.toFixed(2)}`;
    return `$${n.toFixed(4).replace(/0+$/, '').replace(/\.$/, '')}`;
}
/** Run the prefetch for an intent. Concurrent fan-out for price + news. */
export async function prefetchForIntent(intent, client) {
    if (!intent)
        return null;
    const tasks = [];
    let cost = 0;
    // 1. Price
    if (intent.kind === 'ticker') {
        if (intent.assetClass === 'stock') {
            const market = intent.market || 'us';
            tasks.push((async () => {
                // The stock price is a paid x402 call ($0.001) BUT cached for 5 min,
                // so a repeat ticker within the window pays nothing. Book the REAL
                // spend by diffing telemetry around the call (recordFetch fires only
                // on a fresh paid fetch, never a cache hit) instead of hardcoding
                // $0.001 — otherwise a cached hit over-reports the prefetch cost.
                const before = blockrunSpendUsdToday();
                const r = await getStockPrice(intent.symbol, market);
                const paidUsd = Math.max(0, blockrunSpendUsdToday() - before);
                if (typeof r === 'string') {
                    return { ok: false, line: `- ${intent.symbol} (${market}): lookup failed — ${r.slice(0, 80)}`, cost: paidUsd };
                }
                return {
                    ok: true,
                    line: `- ${intent.symbol} (${market}) live price: ${formatUsd(r.price)} (BlockRun Gateway / Pyth)`,
                    cost: paidUsd,
                };
            })());
        }
        else {
            // crypto
            tasks.push(getPrice(intent.symbol, 'crypto').then((r) => {
                if (typeof r === 'string') {
                    return { ok: false, line: `- ${intent.symbol}: lookup failed — ${r.slice(0, 80)}`, cost: 0 };
                }
                const delta = Number.isFinite(r.change24h) ? ` (${r.change24h > 0 ? '+' : ''}${r.change24h.toFixed(2)}% 24h)` : '';
                return {
                    ok: true,
                    line: `- ${intent.symbol} live price: ${formatUsd(r.price)}${delta} (CoinGecko)`,
                    cost: 0,
                };
            }));
        }
    }
    // 2. News, if asked
    if (intent.kind === 'ticker' && intent.wantNews) {
        const query = intent.assetClass === 'stock'
            ? `Why did ${intent.symbol} stock move over the past week? Recent news and catalysts for ${intent.symbol} as of today.`
            : `What are the most important recent news events affecting ${intent.symbol} cryptocurrency in the past week?`;
        tasks.push(exaAnswerTry(query, client).then(({ text, costUsd }) => {
            if (!text) {
                // costUsd is non-zero only when a paid call actually settled but came
                // back empty — count it so spend telemetry never under-reports USDC.
                return { ok: false, line: `- Recent ${intent.symbol} news: ExaAnswer lookup failed`, cost: costUsd };
            }
            return {
                ok: true,
                line: `- Recent ${intent.symbol} news (ExaAnswer synthesized):\n  ${text.replace(/\n/g, '\n  ')}`,
                cost: costUsd,
            };
        }));
    }
    const results = await Promise.all(tasks);
    const anyOk = results.some(r => r.ok);
    cost = results.reduce((s, r) => s + r.cost, 0);
    const lines = results.map(r => r.line).filter(Boolean);
    if (lines.length === 0)
        return null;
    const contextBlock = [
        '[FRANKLIN HARNESS PREFETCH]',
        `The harness automatically fetched live data before your turn. Use these facts as ground truth — do NOT override them with training-data assumptions.`,
        '',
        ...lines,
        '',
    ].join('\n');
    const statusLine = `*Prefetched ${lines.length} source${lines.length === 1 ? '' : 's'} · cost ${formatUsd(cost)}*`;
    return { contextBlock, statusLine, costUsd: cost, anyOk };
}
/** Fallback per-call ExaAnswer estimate when the gateway omits costDollars. */
const EXA_ANSWER_EST_USD = 0.01;
/**
 * Read an ExaAnswer response through BOTH wire shapes — the live BlockRun
 * gateway returns `{ answer, costDollars }` at the TOP level, while older or
 * proxied deployments nest them under `data`. This mirrors the `res.data ?? res`
 * read in src/tools/exa.ts; this prefetch path is the twin of the ExaAnswer
 * tool and historically drifted out of sync, paying the USDC then dropping the
 * answer. Exported for regression tests. `paid` records whether a real x402
 * charge settled, so a paid-but-empty answer still counts against spend.
 */
export function readExaAnswer(raw, paid) {
    // Defensive against a drifted/malformed wire shape: a non-object body or a
    // non-string `answer` must degrade to null text (NOT throw), otherwise the
    // throw is swallowed by exaAnswerTry's catch and a settled paid call is
    // mis-booked as $0 — the exact under-report this function exists to prevent.
    const body = (raw && typeof raw === 'object' ? raw : {});
    const b = (body.data && typeof body.data === 'object' ? body.data : body);
    const text = (typeof b.answer === 'string' ? b.answer : '').slice(0, 600).trim() || null;
    const reported = typeof b.costDollars?.total === 'number' ? b.costDollars.total : undefined;
    const costUsd = reported ?? (paid ? EXA_ANSWER_EST_USD : 0);
    return { text, costUsd };
}
/** Thin wrapper: call ExaAnswer via the gateway, return parsed text + real cost. */
async function exaAnswerTry(query, client) {
    try {
        // Reuse the BlockRun gateway chat endpoint the ExaAnswer tool already uses.
        // We inline the request rather than invoke the capability through the full
        // tool framework because prefetch runs outside the agent loop — no
        // permission prompt, no streaming.
        const { loadChain, API_URLS } = await import('../config.js');
        const chain = loadChain();
        const apiUrl = API_URLS[chain];
        void client; // (future: unify the paid-endpoint client so we reuse wallet caching)
        const res = await fetch(`${apiUrl}/v1/exa/answer`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query }),
        });
        if (res.status === 402) {
            const payHdr = await extractPaymentReq(res);
            if (!payHdr)
                return { text: null, costUsd: 0 };
            const { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK } = await import('@blockrun/llm');
            const paymentRequired = parsePaymentRequired(payHdr);
            let headers = { 'Content-Type': 'application/json' };
            if (chain === 'solana') {
                const wallet = await getOrCreateSolanaWallet();
                const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
                const secretBytes = await solanaKeyToBytes(wallet.privateKey);
                const feePayer = details.extra?.feePayer || details.recipient;
                const payload = await createSolanaPaymentPayload(secretBytes, wallet.address, details.recipient, details.amount, feePayer, {
                    resourceUrl: details.resource?.url || `${apiUrl}/v1/exa/answer`,
                    resourceDescription: 'Franklin prefetch ExaAnswer',
                    maxTimeoutSeconds: details.maxTimeoutSeconds || 60,
                    extra: details.extra,
                });
                headers = { ...headers, 'PAYMENT-SIGNATURE': payload };
            }
            else {
                const wallet = getOrCreateWallet();
                const details = extractPaymentDetails(paymentRequired);
                const payload = await createPaymentPayload(wallet.privateKey, wallet.address, details.recipient, details.amount, details.network || 'eip155:8453', {
                    resourceUrl: details.resource?.url || `${apiUrl}/v1/exa/answer`,
                    resourceDescription: 'Franklin prefetch ExaAnswer',
                    maxTimeoutSeconds: details.maxTimeoutSeconds || 60,
                    extra: details.extra,
                });
                headers = { ...headers, 'PAYMENT-SIGNATURE': payload };
            }
            const res2 = await fetch(`${apiUrl}/v1/exa/answer`, {
                method: 'POST', headers, body: JSON.stringify({ query }),
            });
            if (!res2.ok)
                return { text: null, costUsd: 0 };
            // A 200 here means the x402 charge settled — count it even if empty.
            return readExaAnswer(await res2.json(), true);
        }
        if (!res.ok)
            return { text: null, costUsd: 0 };
        return readExaAnswer(await res.json(), false);
    }
    catch {
        return { text: null, costUsd: 0 };
    }
}
async function extractPaymentReq(response) {
    let header = response.headers.get('payment-required');
    if (!header) {
        try {
            const body = (await response.json());
            if (body.x402 || body.accepts)
                header = btoa(JSON.stringify(body));
        }
        catch { /* ignore */ }
    }
    return header;
}
// ─── Injection helper ────────────────────────────────────────────────────
/**
 * Augment a user message with the prefetch context block prepended. The
 * final model sees the data as part of the "incoming" user turn — no
 * synthetic tool_use fabrication needed, history stays clean.
 */
export function augmentUserMessage(originalInput, prefetch) {
    return {
        role: 'user',
        content: `${prefetch.contextBlock}\n\nOriginal user message:\n${originalInput}`,
    };
}
