/**
 * PredictionMarket — unified access to Polymarket / Kalshi / Limitless /
 * Opinion / Predict.Fun / cross-platform / smart-money / wallet 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.
 *
 *   searchAll          $0.005  search markets across Polymarket+Kalshi+
 *                              Limitless+Opinion+Predict.Fun in one call
 *   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)
 *   leaderboard        $0.001  global Polymarket leaderboard — top wallets by P&L
 *   walletProfile      $0.005  full Polymarket wallet profile (single wallet)
 *                              or batch profiles (comma-separated wallets)
 *   walletPnl          $0.005  P&L summary + realized P&L time series for one
 *                              Polymarket wallet
 *   walletPositions    $0.005  open + historical positions for one Polymarket
 *                              wallet
 *   smartActivity      $0.005  discover markets where high-performing wallets
 *                              are active right now
 *   smartMoney         $0.005  smart-money positioning on one Polymarket
 *                              condition_id (per-market drill-down)
 *
 * 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';
import { recordFetch } from '../trading/providers/telemetry.js';
const TIMEOUT_MS = 30_000;
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 50;
// Per-action price table — mirrors the Predexon openapi.json. Used to feed
// the Markets-tab telemetry ring buffer so prediction-market spend appears
// in "Calls today / Spend today / Recent paid calls" alongside trading calls.
// If a path isn't here we don't record cost — we still record the fetch
// (success/latency) so panel health stays accurate.
const PATH_PRICES = [
    { pattern: /\/v1\/pm\/markets\/search$/, usd: 0.005 },
    { pattern: /\/v1\/pm\/matching-markets/, usd: 0.005 },
    { pattern: /\/v1\/pm\/polymarket\/wallets\//, usd: 0.005 },
    { pattern: /\/v1\/pm\/polymarket\/wallet\//, usd: 0.005 },
    { pattern: /\/v1\/pm\/polymarket\/market\/[^/]+\/smart-money$/, usd: 0.005 },
    { pattern: /\/v1\/pm\/polymarket\/markets\/smart-activity$/, usd: 0.005 },
    { pattern: /\/v1\/pm\/.+/, usd: 0.001 },
];
function priceForPath(path) {
    for (const { pattern, usd } of PATH_PRICES) {
        if (pattern.test(path))
            return usd;
    }
    return 0;
}
// ─── 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 });
    const startedAt = Date.now();
    let costRecorded = 0;
    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 },
            });
            // Only record cost on the post-402 settlement; the initial 402
            // response is free and counting it would double-charge the panel.
            costRecorded = priceForPath(path);
        }
        if (!response.ok) {
            const errText = await response.text().catch(() => '');
            // Surface failed paid calls in the Markets-tab health summary.
            recordFetch({ provider: 'blockrun', endpoint: path, ok: false, latencyMs: Date.now() - startedAt });
            throw new Error(`PredictionMarket ${path} failed (${response.status}): ${errText.slice(0, 600)}`);
        }
        recordFetch({
            provider: 'blockrun',
            endpoint: path,
            ok: true,
            latencyMs: Date.now() - startedAt,
            costUsd: costRecorded > 0 ? costRecorded : undefined,
        });
        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 asNumber(value) {
    if (typeof value === 'number' && Number.isFinite(value))
        return value;
    if (typeof value === 'string' && value.trim() !== '') {
        const n = Number(value);
        if (Number.isFinite(n))
            return n;
    }
    return null;
}
function formatUsd(value) {
    const n = asNumber(value);
    if (n == null)
        return 'n/a';
    if (n >= 1e9)
        return `$${(n / 1e9).toFixed(2)}B`;
    if (n >= 1e6)
        return `$${(n / 1e6).toFixed(2)}M`;
    if (n >= 1e3)
        return `$${(n / 1e3).toFixed(1)}K`;
    return `$${n.toFixed(2)}`;
}
function formatQuantity(value) {
    const n = asNumber(value);
    if (n == null)
        return String(value ?? 'n/a');
    return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(undefined, { maximumFractionDigits: 4 });
}
function formatPct(value, digits = 1) {
    const n = asNumber(value);
    if (n == null)
        return 'n/a';
    const pct = Math.abs(n) > 1 ? n : n * 100;
    return `${pct.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;
        if (Array.isArray(obj.positions))
            return obj.positions;
    }
    return [];
}
function parseWalletsInput(value) {
    return value
        .split(',')
        .map(w => w.trim())
        .filter(Boolean);
}
/**
 * Pick the first usable string from a list of candidate values.
 *
 * Predexon responses sometimes wrap titles/labels inside nested objects
 * (e.g. `position.market = { slug, question, title }` instead of a flat
 * `position.title`). Pre-3.15.75 the formatter `as string` cast these
 * objects and ended up rendering `[object Object]` for every position
 * row — verified 2026-05-06 in a real session.
 *
 * Strategy:
 *   - string → return as-is (after trim)
 *   - object → walk a small set of common name-bearing keys
 *     (title, question, slug, name, label, market_slug) and return the
 *     first one that yields a string
 *   - anything else (number / array / null) → skip
 *   - all candidates exhausted → undefined
 */
function pickString(...candidates) {
    const NAME_KEYS = ['title', 'question', 'slug', 'name', 'label', 'market_slug', 'event_title'];
    for (const c of candidates) {
        if (typeof c === 'string') {
            const trimmed = c.trim();
            if (trimmed)
                return trimmed;
        }
        else if (c && typeof c === 'object' && !Array.isArray(c)) {
            const obj = c;
            for (const k of NAME_KEYS) {
                const v = obj[k];
                if (typeof v === 'string' && v.trim())
                    return v.trim();
            }
        }
    }
    return undefined;
}
async function execute(input, ctx) {
    const { action, search, status, sort, limit, conditionId, wallets, granularity } = input;
    const cappedLimit = Math.min(Math.max(1, limit ?? DEFAULT_LIMIT), MAX_LIMIT);
    if (!action) {
        return {
            output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | walletPnl | walletPositions | smartActivity | smartMoney)',
            isError: true,
        };
    }
    try {
        switch (action) {
            case 'searchAll': {
                // One $0.005 call across 5 platforms — Polymarket, Kalshi, Limitless,
                // Opinion, Predict.Fun. The right entry point for "is there a market
                // on X anywhere?" — beats firing per-platform searches in parallel.
                // Predexon expects `q` for the search term — verified 2026-05-06 from
                // a live 422: {"detail":[{"type":"missing","loc":["query","q"]}]}.
                // Public input field stays `search` for ergonomic consistency with
                // searchPolymarket / searchKalshi; rename on the wire.
                const raw = await getWithPayment('/v1/pm/markets/search', {
                    q: search,
                    status,
                    sort,
                    limit: cappedLimit,
                }, ctx);
                // Predexon returns either a flat list or per-platform buckets.
                // Try the bucket shape first; fall back to a flat list.
                const lines = [
                    `## Cross-platform market search` + (search ? ` · "${search}"` : ''),
                    '_Searched Polymarket, Kalshi, Limitless, Opinion, Predict.Fun in one call._',
                    '',
                ];
                if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
                    const obj = raw;
                    const platforms = ['polymarket', 'kalshi', 'limitless', 'opinion', 'predictfun', 'predict_fun'];
                    let totalShown = 0;
                    for (const p of platforms) {
                        const list = unwrapList(obj[p]);
                        if (list.length === 0)
                            continue;
                        const remaining = cappedLimit - totalShown;
                        if (remaining <= 0)
                            break;
                        const shown = list.slice(0, Math.min(5, remaining));
                        lines.push(`### ${p}`);
                        shown.forEach((m, i) => {
                            const title = pickString(m.title, m.question, m.market, m.event, m.market_slug, m.slug, m.ticker) ?? 'untitled';
                            const id = pickString(m.condition_id, m.ticker, m.id);
                            const idTag = id ? ` · \`${String(id).slice(0, 18)}…\`` : '';
                            const vol = m.volume != null ? ` · vol ${formatUsd(m.volume)}` : '';
                            lines.push(`${i + 1}. ${title}${idTag}${vol}`);
                            totalShown++;
                        });
                        lines.push('');
                    }
                    if (totalShown === 0) {
                        // Bucket shape but empty — fall back to flat-list interpretation.
                        const flat = unwrapList(raw);
                        if (flat.length === 0) {
                            return { output: 'No markets matched across any platform.' };
                        }
                        flat.slice(0, cappedLimit).forEach((m, i) => {
                            const title = pickString(m.title, m.question, m.market, m.event, m.market_slug, m.slug, m.ticker) ?? 'untitled';
                            const platform = pickString(m.platform, m.source) ?? 'unknown';
                            lines.push(`${i + 1}. **[${platform}]** ${title}`);
                        });
                    }
                }
                else {
                    const flat = unwrapList(raw);
                    if (flat.length === 0) {
                        return { output: 'No markets matched across any platform.' };
                    }
                    flat.slice(0, cappedLimit).forEach((m, i) => {
                        const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
                        const platform = (m.platform || m.source || 'unknown');
                        lines.push(`${i + 1}. **[${platform}]** ${title}`);
                    });
                }
                lines.push(`_$0.005 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'leaderboard': {
                // Global top-wallet ranking. Cheap ($0.001) — the right answer to
                // "who's making money on Polymarket" / "who should I follow".
                const raw = await getWithPayment('/v1/pm/polymarket/leaderboard', {
                    limit: cappedLimit,
                    sort,
                }, ctx);
                const rows = unwrapList(raw);
                if (rows.length === 0) {
                    return { output: 'No leaderboard data returned.' };
                }
                const lines = [
                    `## Polymarket leaderboard — top ${rows.length} wallet${rows.length === 1 ? '' : 's'}`,
                    '',
                ];
                rows.forEach((r, i) => {
                    const wallet = pickString(r.wallet, r.address, r.proxy_wallet, r.proxyWallet) ?? 'unknown';
                    const w = wallet.length > 12
                        ? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
                        : wallet;
                    const pnl = r.pnl ?? r.realized_pnl ?? r.total_pnl;
                    const volume = r.volume ?? r.total_volume;
                    const winRate = r.win_rate ?? r.winRate;
                    const name = pickString(r.name, r.handle, r.username);
                    const handle = name ? ` (${name})` : '';
                    const parts = [];
                    if (pnl != null)
                        parts.push(`P&L ${formatUsd(pnl)}`);
                    if (volume != null)
                        parts.push(`vol ${formatUsd(volume)}`);
                    if (winRate != null)
                        parts.push(`win ${formatPct(winRate, 0)}`);
                    lines.push(`${i + 1}. \`${w}\`${handle}` + (parts.length > 0 ? ` — ${parts.join(' · ')}` : ''));
                });
                lines.push('', `_$0.001 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'walletProfile': {
                if (!wallets || !wallets.trim()) {
                    return {
                        output: 'Error: `wallets` is required for walletProfile (single address or comma-separated list of Polymarket wallet addresses)',
                        isError: true,
                    };
                }
                // Smart dispatch: a single wallet → /wallet/{addr} (full profile,
                // labels, scores, stats); a comma-list → /wallets/profiles (batch).
                // The 3.15.70 ship hit the BATCH endpoint for everything and got 422
                // for the single-wallet case; the gateway team confirmed 2026-05-06
                // the right surface for "analyze this trader" is the path-parameter
                // single-wallet endpoint, not the batch query-param one.
                const parsedWallets = parseWalletsInput(wallets);
                if (parsedWallets.length === 0) {
                    return {
                        output: 'Error: `wallets` must include at least one Polymarket wallet address',
                        isError: true,
                    };
                }
                const list = parsedWallets.join(',');
                const isBatch = parsedWallets.length > 1;
                const raw = isBatch
                    ? await getWithPayment('/v1/pm/polymarket/wallets/profiles', {
                        addresses: list,
                    }, ctx)
                    : await getWithPayment(`/v1/pm/polymarket/wallet/${encodeURIComponent(list)}`, {}, ctx);
                // Single-wallet path returns a single profile object; batch returns
                // an array (or {data:[]}). unwrapList handles the batch shape but
                // returns [] for a bare object — wrap explicitly so the formatter
                // below sees the single profile.
                if (process.env.FRANKLIN_PM_DEBUG === '1') process.stderr.write('[pm-debug] walletProfile raw: ' + JSON.stringify(raw).slice(0,1500) + '\n');
        const profiles = isBatch
                    ? unwrapList(raw)
                    : (raw && typeof raw === 'object' ? [raw] : []);
                if (profiles.length === 0) {
                    return { output: `No profile data returned for: ${wallets}` };
                }
                const lines = [
                    `## Polymarket wallet profile${profiles.length === 1 ? '' : 's'} — ${profiles.length}`,
                    '',
                ];
                profiles.forEach((p, i) => {
                    const wallet = pickString(p.wallet, p.address, p.proxy_wallet, p.proxyWallet) ?? 'unknown';
                    const w = wallet.length > 12
                        ? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
                        : wallet;
                    const name = pickString(p.name, p.handle, p.username);
                    const pnl = p.pnl ?? p.realized_pnl ?? p.total_pnl;
                    const unrealized = p.unrealized_pnl;
                    const volume = p.volume ?? p.total_volume;
                    const positions = p.positions_count ?? p.open_positions;
                    const winRate = p.win_rate ?? p.winRate;
                    lines.push(`${i + 1}. \`${w}\`` + (name ? ` (${name})` : ''));
                    const stats = [];
                    if (pnl != null)
                        stats.push(`P&L ${formatUsd(pnl)}`);
                    if (unrealized != null)
                        stats.push(`unrealized ${formatUsd(unrealized)}`);
                    if (volume != null)
                        stats.push(`vol ${formatUsd(volume)}`);
                    if (positions != null)
                        stats.push(`${positions} open`);
                    if (winRate != null)
                        stats.push(`win ${formatPct(winRate, 0)}`);
                    if (stats.length > 0)
                        lines.push(`   ${stats.join(' · ')}`);
                });
                lines.push('', `_$0.005 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'walletPnl': {
                // Single-wallet P&L summary + time series.
                // Predexon path: /v1/pm/polymarket/wallet/pnl/{wallet} — Tier 2 ($0.005).
                if (!wallets || !wallets.trim()) {
                    return {
                        output: 'Error: `wallets` is required for walletPnl (single Polymarket wallet address)',
                        isError: true,
                    };
                }
                const parsedWallets = parseWalletsInput(wallets);
                if (parsedWallets.length !== 1) {
                    return {
                        output: 'Error: walletPnl accepts exactly one wallet address. For multiple wallets, call walletPnl once per address in parallel.',
                        isError: true,
                    };
                }
                const wallet = parsedWallets[0];
                // Predexon requires `granularity` from the enum {day, week, month,
                // year, all} — verified 2026-05-06 in two live 422 turns. Default
                // `day`; agent can override via input field for longer aggregations.
                const raw = await getWithPayment(`/v1/pm/polymarket/wallet/pnl/${encodeURIComponent(wallet)}`, { granularity: granularity ?? 'day' }, ctx);
                if (process.env.FRANKLIN_PM_DEBUG === '1') process.stderr.write('[pm-debug] walletPnl raw: ' + JSON.stringify(raw).slice(0,1500) + '\n');
        if (!raw || typeof raw !== 'object') {
                    return { output: `No P&L data returned for ${wallet}` };
                }
                const data = raw;
                const realized = data.realized_pnl ?? data.realizedPnl ?? data.total_pnl ?? data.pnl;
                const unrealized = data.unrealized_pnl ?? data.unrealizedPnl;
                const total = data.total_value ?? data.totalValue ?? data.equity;
                const volume = data.volume ?? data.total_volume;
                const winRate = data.win_rate ?? data.winRate;
                const w = wallet.length > 12 ? `${wallet.slice(0, 8)}…${wallet.slice(-4)}` : wallet;
                const lines = [`## Polymarket wallet P&L — \`${w}\``, ''];
                const summary = [];
                if (realized != null)
                    summary.push(`realized ${formatUsd(realized)}`);
                if (unrealized != null)
                    summary.push(`unrealized ${formatUsd(unrealized)}`);
                if (total != null)
                    summary.push(`equity ${formatUsd(total)}`);
                if (volume != null)
                    summary.push(`vol ${formatUsd(volume)}`);
                if (winRate != null)
                    summary.push(`win ${formatPct(winRate, 0)}`);
                if (summary.length > 0)
                    lines.push(summary.join(' · '));
                // Optional time series — show recent points compactly if present.
                const series = (data.series ?? data.history ?? data.daily);
                if (Array.isArray(series) && series.length > 0) {
                    lines.push('', `**Recent points** (latest ${Math.min(7, series.length)}):`);
                    series.slice(-7).forEach(pt => {
                        const t = (pt.date ?? pt.ts ?? pt.timestamp);
                        const v = (pt.pnl ?? pt.value ?? pt.cumulative_pnl);
                        if (t != null && v != null) {
                            const tStr = typeof t === 'number' ? new Date(t).toISOString().slice(0, 10) : String(t).slice(0, 10);
                            lines.push(`- ${tStr} · ${formatUsd(v)}`);
                        }
                    });
                }
                lines.push('', `_$0.005 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'walletPositions': {
                // Single-wallet positions (open + historical).
                // Predexon path: /v1/pm/polymarket/wallet/positions/{wallet} — Tier 2 ($0.005).
                if (!wallets || !wallets.trim()) {
                    return {
                        output: 'Error: `wallets` is required for walletPositions (single Polymarket wallet address)',
                        isError: true,
                    };
                }
                const parsedWallets = parseWalletsInput(wallets);
                if (parsedWallets.length !== 1) {
                    return {
                        output: 'Error: walletPositions accepts exactly one wallet address. For multiple wallets, call walletPositions once per address in parallel.',
                        isError: true,
                    };
                }
                const wallet = parsedWallets[0];
                const raw = await getWithPayment(`/v1/pm/polymarket/wallet/positions/${encodeURIComponent(wallet)}`, { limit: cappedLimit }, ctx);
                const positions = unwrapList(raw);
                if (positions.length === 0) {
                    return { output: `No positions returned for ${wallet}` };
                }
                const w = wallet.length > 12 ? `${wallet.slice(0, 8)}…${wallet.slice(-4)}` : wallet;
                const lines = [
                    `## Polymarket positions — \`${w}\` — ${positions.length} position${positions.length === 1 ? '' : 's'}`,
                    '',
                ];
                // Predexon returns each position as a nested record:
                //   { market: {title, side_label, ...},
                //     position: {shares, avg_entry_price, total_cost_usd, ...},
                //     current: {price, value_usd},
                //     pnl: {unrealized_usd, unrealized_pct, realized_usd} }
                // Verified 2026-05-06 via FRANKLIN_PM_DEBUG=1 dump. Walk the four
                // sub-objects rather than assuming flat fields. Keep flat-field
                // fallbacks too in case the response shape changes or the user's
                // gateway version returns a flatter format.
                positions.slice(0, cappedLimit).forEach((p, i) => {
                    const market = (p.market && typeof p.market === 'object' ? p.market : {});
                    const position = (p.position && typeof p.position === 'object' ? p.position : {});
                    const current = (p.current && typeof p.current === 'object' ? p.current : {});
                    const pnlObj = (p.pnl && typeof p.pnl === 'object' ? p.pnl : {});
                    const title = pickString(market.title, market.question, p.title, p.question, market.market_slug, p.market_slug) ?? 'untitled';
                    const outcome = pickString(market.side_label, market.side, p.outcome, p.side);
                    const shares = position.shares ?? position.total_shares_bought ?? p.size ?? p.shares;
                    const avgPrice = position.avg_entry_price ?? p.avg_price ?? p.avgPrice;
                    const currentValue = current.value_usd ?? p.current_value ?? p.currentValue ?? p.value;
                    const pnl = pnlObj.unrealized_usd ?? pnlObj.realized_usd ?? p.cashPnl ?? p.pnl;
                    const pnlPct = pnlObj.unrealized_pct ?? pnlObj.realized_pct ?? p.percentPnl ?? p.percent_pnl;
                    const parts = [];
                    if (outcome)
                        parts.push(outcome);
                    if (shares != null)
                        parts.push(`${formatQuantity(shares)} shares`);
                    if (avgPrice != null)
                        parts.push(`avg ${formatPct(avgPrice)}`);
                    if (currentValue != null)
                        parts.push(`now ${formatUsd(currentValue)}`);
                    if (pnl != null) {
                        const pctStr = pnlPct != null ? ` (${formatPct(pnlPct, 1)})` : '';
                        parts.push(`P&L ${formatUsd(pnl)}${pctStr}`);
                    }
                    lines.push(`${i + 1}. **${title}** — ${parts.join(' · ')}`);
                });
                lines.push('', `_$0.005 paid via x402._`);
                return { output: lines.join('\n') };
            }
            case 'smartActivity': {
                // "Discover markets where high-performing wallets are active right now."
                // Complements `smartMoney`: this discovers interesting markets across
                // the venue; smartMoney drills into one condition_id.
                const raw = await getWithPayment('/v1/pm/polymarket/markets/smart-activity', {
                    limit: cappedLimit,
                    search,
                }, ctx);
                const rows = unwrapList(raw);
                if (rows.length === 0) {
                    return { output: 'No smart-money activity recorded right now.' };
                }
                const lines = [
                    `## Smart-money activity — ${rows.length} market${rows.length === 1 ? '' : 's'}`,
                    '_Markets where high-P&L Polymarket wallets are positioning right now._',
                    '',
                ];
                rows.forEach((r, i) => {
                    const title = pickString(r.question, r.title, r.market, r.event, r.market_slug, r.slug) ?? 'untitled';
                    const cid = pickString(r.condition_id, r.id);
                    const cidTag = cid ? ` · \`${String(cid).slice(0, 14)}…\`` : '';
                    const smartCount = r.smart_wallets_count ?? r.wallet_count;
                    const netFlow = r.net_size ?? r.net_yes_size;
                    const stats = [];
                    if (smartCount != null)
                        stats.push(`${smartCount} smart wallet${smartCount === 1 ? '' : 's'}`);
                    if (netFlow != null)
                        stats.push(`net ${formatUsd(netFlow)}`);
                    lines.push(`${i + 1}. **${title}**${cidTag}` + (stats.length > 0 ? `\n   ${stats.join(' · ')}` : ''));
                });
                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 or smartActivity call)',
                        isError: true,
                    };
                }
                // Per-market drill-down. Official live registry:
                // /api/v1/pm/polymarket/market/:condition_id/smart-money
                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) {
                    lines.push(`**Net flow:** YES ${formatUsd(data.net_yes_size)} / NO ${formatUsd(data.net_no_size)}`);
                }
                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') };
            }
            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') };
            }
            default:
                return {
                    output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, walletPnl, walletPositions, smartActivity, 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" / "is there a market on Z" / "follow this trader" question. ' +
            'Actions: ' +
            '`searchAll` (search markets across Polymarket+Kalshi+Limitless+Opinion+Predict.Fun in one call — $0.005), ' +
            '`searchPolymarket` (Polymarket only, supports sort+status — $0.001), ' +
            '`searchKalshi` (Kalshi only, supports sort+status — $0.001), ' +
            '`crossPlatform` (matched market pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
            '`leaderboard` (global top wallets by P&L on Polymarket — $0.001), ' +
            '`walletProfile` (full Polymarket wallet profile — labels, scores, stats. Single address → /wallet/{addr}; comma-list → batch /wallets/profiles — $0.005), ' +
            '`walletPnl` (single Polymarket wallet P&L summary + time series — $0.005), ' +
            '`walletPositions` (single Polymarket wallet positions — open + historical with P&L per position — $0.005), ' +
            '`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005), ' +
            '`smartMoney` (smart-money positioning on one Polymarket condition_id — $0.005). ' +
            'Default routing: ' +
            '"is there a market on X anywhere" → searchAll. ' +
            '"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
            '"analyze this wallet / can I copy this trader / copy trade / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
            '"what are smart traders betting on right now" → smartActivity. ' +
            '"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
            '"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
        input_schema: {
            type: 'object',
            properties: {
                action: {
                    type: 'string',
                    enum: [
                        'searchAll',
                        'searchPolymarket',
                        'searchKalshi',
                        'crossPlatform',
                        'leaderboard',
                        'walletProfile',
                        'walletPnl',
                        'walletPositions',
                        'smartActivity',
                        'smartMoney',
                    ],
                    description: 'Which prediction-market query to run. See tool description for cost per action.',
                },
                search: {
                    type: 'string',
                    description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile/walletPnl/walletPositions/smartMoney.',
                },
                status: {
                    type: 'string',
                    description: 'Polymarket: active | closed | archived (default active). Kalshi: open | closed (default open). Forwarded to searchAll where supported.',
                },
                sort: {
                    type: 'string',
                    description: 'Polymarket: volume | liquidity | created (default volume). Kalshi: volume | open_interest | price_desc | price_asc | close_time (default volume). leaderboard: pnl | volume | win_rate (gateway-defined).',
                },
                limit: {
                    type: 'number',
                    description: `Max results (default ${DEFAULT_LIMIT}, hard cap ${MAX_LIMIT}).`,
                },
                wallets: {
                    type: 'string',
                    description: 'For walletProfile: a single Polymarket wallet address, or a comma-separated list of addresses for batch lookup.',
                },
                conditionId: {
                    type: 'string',
                    description: 'For smartMoney: Polymarket condition_id from searchPolymarket or smartActivity.',
                },
                granularity: {
                    type: 'string',
                    enum: ['day', 'week', 'month', 'year', 'all'],
                    description: 'For walletPnl: time bucket for the P&L series. Default day.',
                },
            },
            required: ['action'],
        },
    },
    execute,
    concurrent: true,
};
