/**
 * PredictionMarket — unified access to Polymarket / Kalshi / cross-platform
 * matching / smart-money endpoints via the BlockRun gateway. Each call
 * settles via x402 against the user's USDC wallet.
 *
 * Powered server-side by Predexon; surfaced to the agent as a single
 * action-dispatched tool so the inventory stays small. Keep one cohesive
 * tool — the way TradingMarket bundles 6 actions — instead of forty
 * one-shot capabilities, otherwise weak models start hallucinating tool
 * names.
 *
 *   searchPolymarket   $0.001  query Polymarket markets (event filter, sort)
 *   searchKalshi       $0.001  query Kalshi markets
 *   crossPlatform      $0.005  matching market pairs across Polymarket+Kalshi
 *                              (the arbitrage / consensus signal)
 *   smartMoney         $0.005  smart-money positioning on one Polymarket
 *                              condition_id (top wallet flow + side bias)
 *
 * Output is filtered + truncated on the way back so a single call never
 * dumps 100 markets into the agent's context. Default 20 rows; agents that
 * need more should narrow the search.
 */
import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
import { loadChain, API_URLS, VERSION } from '../config.js';
import { logger } from '../logger.js';
const TIMEOUT_MS = 30_000;
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 50;
// ─── Shared GET-with-x402 flow ────────────────────────────────────────────
async function getWithPayment(path, query, ctx) {
    const chain = loadChain();
    const apiUrl = API_URLS[chain];
    const qs = new URLSearchParams();
    for (const [k, v] of Object.entries(query)) {
        if (v == null || v === '')
            continue;
        qs.set(k, String(v));
    }
    const queryStr = qs.toString();
    const endpoint = `${apiUrl}${path}${queryStr ? `?${queryStr}` : ''}`;
    const headers = {
        Accept: 'application/json',
        'User-Agent': `franklin/${VERSION}`,
    };
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
    const onAbort = () => controller.abort();
    ctx.abortSignal.addEventListener('abort', onAbort, { once: true });
    try {
        let response = await fetch(endpoint, { method: 'GET', signal: controller.signal, headers });
        if (response.status === 402) {
            const paymentHeaders = await signPayment(response, chain, endpoint);
            if (!paymentHeaders) {
                throw new Error('Payment signing failed — check wallet balance');
            }
            response = await fetch(endpoint, {
                method: 'GET',
                signal: controller.signal,
                headers: { ...headers, ...paymentHeaders },
            });
        }
        if (!response.ok) {
            const errText = await response.text().catch(() => '');
            throw new Error(`PredictionMarket ${path} failed (${response.status}): ${errText.slice(0, 200)}`);
        }
        return (await response.json());
    }
    finally {
        clearTimeout(timeout);
        ctx.abortSignal.removeEventListener('abort', onAbort);
    }
}
async function signPayment(response, chain, endpoint) {
    try {
        const paymentHeader = await extractPaymentReq(response);
        if (!paymentHeader)
            return null;
        if (chain === 'solana') {
            const wallet = await getOrCreateSolanaWallet();
            const paymentRequired = parsePaymentRequired(paymentHeader);
            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 || endpoint,
                resourceDescription: details.resource?.description || 'Franklin PredictionMarket call',
                maxTimeoutSeconds: details.maxTimeoutSeconds || 60,
                extra: details.extra,
            });
            return { 'PAYMENT-SIGNATURE': payload };
        }
        const wallet = await getOrCreateWallet();
        const paymentRequired = parsePaymentRequired(paymentHeader);
        const details = extractPaymentDetails(paymentRequired);
        const payload = await createPaymentPayload(wallet.privateKey, wallet.address, details.recipient, details.amount, details.network || 'eip155:8453', {
            resourceUrl: details.resource?.url || endpoint,
            resourceDescription: details.resource?.description || 'Franklin PredictionMarket call',
            maxTimeoutSeconds: details.maxTimeoutSeconds || 60,
            extra: details.extra,
        });
        return { 'PAYMENT-SIGNATURE': payload };
    }
    catch (err) {
        logger.warn(`[franklin] PredictionMarket payment error: ${err.message}`);
        return null;
    }
}
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;
}
// ─── Formatting helpers ────────────────────────────────────────────────────
function formatUsd(value) {
    if (value == null || !Number.isFinite(value))
        return 'n/a';
    if (value >= 1e9)
        return `$${(value / 1e9).toFixed(2)}B`;
    if (value >= 1e6)
        return `$${(value / 1e6).toFixed(2)}M`;
    if (value >= 1e3)
        return `$${(value / 1e3).toFixed(1)}K`;
    return `$${value.toFixed(2)}`;
}
function formatPct(value, digits = 1) {
    if (value == null || !Number.isFinite(value))
        return 'n/a';
    return `${(value * 100).toFixed(digits)}%`;
}
// API responses sometimes come wrapped as `{data: [...], pagination: ...}`,
// other times as a bare array. Normalise to an array.
function unwrapList(raw) {
    if (Array.isArray(raw))
        return raw;
    if (raw && typeof raw === 'object') {
        const obj = raw;
        if (Array.isArray(obj.data))
            return obj.data;
        if (Array.isArray(obj.markets))
            return obj.markets;
        if (Array.isArray(obj.pairs))
            return obj.pairs;
        if (Array.isArray(obj.results))
            return obj.results;
    }
    return [];
}
async function execute(input, ctx) {
    const { action, search, status, sort, limit, conditionId } = input;
    const cappedLimit = Math.min(Math.max(1, limit ?? DEFAULT_LIMIT), MAX_LIMIT);
    if (!action) {
        return { output: 'Error: action is required (searchPolymarket | searchKalshi | crossPlatform | smartMoney)', isError: true };
    }
    try {
        switch (action) {
            case 'searchPolymarket': {
                const raw = await getWithPayment('/v1/pm/polymarket/markets', {
                    search,
                    status: status ?? 'active',
                    sort: sort ?? 'volume',
                    limit: cappedLimit,
                }, ctx);
                const markets = unwrapList(raw);
                if (markets.length === 0) {
                    return { output: 'No Polymarket markets matched the filters.' };
                }
                const lines = [
                    `## Polymarket — ${markets.length} market${markets.length === 1 ? '' : 's'}` +
                        (search ? ` · search="${search}"` : '') +
                        (status ? ` · status=${status}` : '') +
                        ` · sort=${sort ?? 'volume'}`,
                    '',
                ];
                markets.forEach((m, i) => {
                    const yesPx = m.outcomes && m.outcome_prices && m.outcomes.length === m.outcome_prices.length
                        ? m.outcomes.map((o, j) => `${o}=${formatPct(m.outcome_prices[j])}`).join(' / ')
                        : 'n/a';
                    const cid = m.condition_id ? ` · condition_id=\`${m.condition_id.slice(0, 14)}…\`` : '';
                    lines.push(`${i + 1}. **${m.question || m.market_slug || 'untitled'}**${cid}\n` +
                        `   prices: ${yesPx} · vol: ${formatUsd(m.volume)} · liq: ${formatUsd(m.liquidity)}` +
                        (m.end_date ? ` · ends ${m.end_date.slice(0, 10)}` : ''));
                });
                lines.push('', `_$0.001 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'searchKalshi': {
                const raw = await getWithPayment('/v1/pm/kalshi/markets', {
                    search,
                    status: status ?? 'open',
                    sort: sort ?? 'volume',
                    limit: cappedLimit,
                }, ctx);
                const markets = unwrapList(raw);
                if (markets.length === 0) {
                    return { output: 'No Kalshi markets matched the filters.' };
                }
                const lines = [
                    `## Kalshi — ${markets.length} market${markets.length === 1 ? '' : 's'}` +
                        (search ? ` · search="${search}"` : '') +
                        ` · status=${status ?? 'open'} · sort=${sort ?? 'volume'}`,
                    '',
                ];
                markets.forEach((m, i) => {
                    // Kalshi quotes prices in cents (0–100). Surface them as a tight
                    // bid/ask so the agent can read implied probability at a glance.
                    const bid = m.yes_bid != null ? `${m.yes_bid}¢` : 'n/a';
                    const ask = m.yes_ask != null ? `${m.yes_ask}¢` : 'n/a';
                    const ticker = m.ticker ? ` · ticker=\`${m.ticker}\`` : '';
                    lines.push(`${i + 1}. **${m.title || m.ticker || 'untitled'}**${ticker}\n` +
                        `   yes ${bid}/${ask} · vol: ${m.volume?.toLocaleString() ?? 'n/a'} · OI: ${m.open_interest?.toLocaleString() ?? 'n/a'}` +
                        (m.close_time ? ` · closes ${m.close_time.slice(0, 10)}` : ''));
                });
                lines.push('', `_$0.001 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'crossPlatform': {
                const raw = await getWithPayment('/v1/pm/matching-markets/pairs', {
                    limit: cappedLimit,
                }, ctx);
                const pairs = unwrapList(raw);
                if (pairs.length === 0) {
                    return { output: 'No matched market pairs available right now.' };
                }
                const lines = [
                    `## Cross-platform matched pairs — ${pairs.length}`,
                    '_Polymarket ↔ Kalshi equivalent markets. Use these to compare implied probabilities across venues._',
                    '',
                ];
                pairs.forEach((p, i) => {
                    const sim = p.similarity != null ? ` · similarity ${formatPct(p.similarity, 0)}` : '';
                    lines.push(`${i + 1}. **Polymarket:** ${p.polymarket_question || '(untitled)'}\n` +
                        `   **Kalshi:** ${p.kalshi_title || '(untitled)'}` +
                        (p.kalshi_ticker ? ` · ticker=\`${p.kalshi_ticker}\`` : '') +
                        sim);
                });
                lines.push('', `_$0.005 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'smartMoney': {
                if (!conditionId) {
                    return { output: 'Error: conditionId is required for smartMoney (Polymarket condition_id from a prior searchPolymarket call)', isError: true };
                }
                const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
                const data = await getWithPayment(path, {}, ctx);
                const buyers = (data.buyers ?? []).slice(0, 5);
                const sellers = (data.sellers ?? []).slice(0, 5);
                const lines = [
                    `## Smart money — \`${conditionId.slice(0, 14)}…\``,
                ];
                if (data.net_yes_size != null || data.net_no_size != null) {
                    const yesSize = formatUsd(data.net_yes_size);
                    const noSize = formatUsd(data.net_no_size);
                    lines.push(`**Net flow:** YES ${yesSize} / NO ${noSize}`);
                }
                if (buyers.length > 0) {
                    lines.push('', '**Top buyers**');
                    buyers.forEach((b, i) => {
                        const w = b.wallet ? `${b.wallet.slice(0, 8)}…${b.wallet.slice(-4)}` : 'unknown';
                        lines.push(`${i + 1}. ${w} — ${formatUsd(b.size)} on ${b.outcome ?? 'unknown side'}`);
                    });
                }
                if (sellers.length > 0) {
                    lines.push('', '**Top sellers**');
                    sellers.forEach((s, i) => {
                        const w = s.wallet ? `${s.wallet.slice(0, 8)}…${s.wallet.slice(-4)}` : 'unknown';
                        lines.push(`${i + 1}. ${w} — ${formatUsd(s.size)} on ${s.outcome ?? 'unknown side'}`);
                    });
                }
                if (buyers.length === 0 && sellers.length === 0) {
                    lines.push('No smart-money flow recorded for this market yet.');
                }
                lines.push('', `_$0.005 paid via x402._`);
                return { output: lines.join('\n') };
            }
            default:
                return {
                    output: `Error: unknown action "${action}". Use: searchPolymarket, searchKalshi, crossPlatform, smartMoney`,
                    isError: true,
                };
        }
    }
    catch (err) {
        return { output: `Error: ${err.message}`, isError: true };
    }
}
export const predictionMarketCapability = {
    spec: {
        name: 'PredictionMarket',
        description: 'Real prediction market data via the BlockRun gateway (powered by Predexon). Use for any "what are the odds of X" / "Polymarket on Y" / "Kalshi market for Z" question. ' +
            'Actions: ' +
            '`searchPolymarket` (search Polymarket markets — $0.001), ' +
            '`searchKalshi` (search Kalshi markets — $0.001), ' +
            '`crossPlatform` (matched pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
            '`smartMoney` (smart-money positioning on a Polymarket condition_id — $0.005). ' +
            'Polymarket and Kalshi are the two largest legit prediction markets; cross-platform matching is unique to BlockRun. ' +
            'For "should I bet on X" / "what does the market price imply": run searchPolymarket AND searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
        input_schema: {
            type: 'object',
            properties: {
                action: {
                    type: 'string',
                    enum: ['searchPolymarket', 'searchKalshi', 'crossPlatform', 'smartMoney'],
                    description: 'Which prediction-market query to run. See tool description for cost per action.',
                },
                search: {
                    type: 'string',
                    description: 'Search query (3-100 chars). Used by searchPolymarket / searchKalshi. Skip for crossPlatform/smartMoney.',
                },
                status: {
                    type: 'string',
                    description: 'Polymarket: active | closed | archived (default active). Kalshi: open | closed (default open).',
                },
                sort: {
                    type: 'string',
                    description: 'Polymarket: volume | liquidity | created (default volume). Kalshi: volume | open_interest | price_desc | price_asc | close_time (default volume).',
                },
                limit: {
                    type: 'number',
                    description: `Max results (default ${DEFAULT_LIMIT}, hard cap ${MAX_LIMIT}).`,
                },
                conditionId: {
                    type: 'string',
                    description: 'Polymarket condition_id. Required for smartMoney. Get one from a prior searchPolymarket call.',
                },
            },
            required: ['action'],
        },
    },
    execute,
    concurrent: true,
};
